Repository: whitequark/ipaddr.js
Pull request #150 (2021-01-06 05:01:37)

add Changes.md, bump version to 2.0.1
I'm looking forward to a release with #142 included. :-) 


Patch:
diff --git a/Changes.md b/Changes.md
new file mode 100644
index 0000000..8782166
--- /dev/null
+++ b/Changes.md
@@ -0,0 +1,20 @@
+
+### 2.0.1 - 2020-01-DD
+
+- add support for deprecated IPv4 compatible IPv6 addresses #142
+- drop node 8 testing, add v14
+- fix parseInt for decimal, octal, hex
+- add support for classful (2,3 octet) IPv4
+
+
+### 2.0.0 - 2019-10-13
+
+- use es6 templates instead of concatenated strings
+- lint: update tests with no-var
+- lint: allow no-cond-assign with extra parens
+- es6: replace var with const/let
+- update README with es6 examples #125
+- replace nodeunit with mocha
+- rewrite in JS, drop CoffeeScript
+
+### 1.9.1 - 2019-07-03
\ No newline at end of file
diff --git a/ipaddr.min.js b/ipaddr.min.js
index 68821d2..342ab97 100644
--- a/ipaddr.min.js
+++ b/ipaddr.min.js
@@ -1,1 +1,1 @@
-!function(t){!function(t){"use strict";const r="(0?\\d+|0x[a-f0-9]+)",e={fourOctet:new RegExp(`^${r}\\.${r}\\.${r}\\.${r}$`,"i"),threeOctet:new RegExp(`^${r}\\.${r}\\.${r}$`,"i"),twoOctet:new RegExp(`^${r}\\.${r}$`,"i"),longValue:new RegExp(`^${r}$`,"i")},n=new RegExp("^0[0-7]+$","i"),i=new RegExp("^0x[a-f0-9]+$","i"),o="(?:[0-9a-f]+::?)+",s={zoneIndex:new RegExp("%[0-9a-z]{1,}","i"),native:new RegExp(`^(::)?(${o})?([0-9a-f]+)?(::)?(%[0-9a-z]{1,})?$`,"i"),deprecatedTransitional:new RegExp(`^(?:::)(${r}\\.${r}\\.${r}\\.${r}(%[0-9a-z]{1,})?)$`,"i"),transitional:new RegExp(`^((?:${o})|(?:::)(?:${o})?)${r}\\.${r}\\.${r}\\.${r}(%[0-9a-z]{1,})?$`,"i")};function a(t,r){if(t.indexOf("::")!==t.lastIndexOf("::"))return null;let e,n,i=0,o=-1,a=(t.match(s.zoneIndex)||[])[0];for(a&&(a=a.substring(1),t=t.replace(/%.+$/,""));(o=t.indexOf(":",o+1))>=0;)i++;if("::"===t.substr(0,2)&&i--,"::"===t.substr(-2,2)&&i--,i>r)return null;for(n=r-i,e=":";n--;)e+="0:";return":"===(t=t.replace("::",e))[0]&&(t=t.slice(1)),":"===t[t.length-1]&&(t=t.slice(0,-1)),{parts:r=function(){const r=t.split(":"),e=[];for(let t=0;t<r.length;t++)e.push(parseInt(r[t],16));return e}(),zoneId:a}}function p(t,r,e,n){if(t.length!==r.length)throw new Error("ipaddr: cannot match CIDR for objects with different lengths");let i,o=0;for(;n>0;){if((i=e-n)<0&&(i=0),t[o]>>i!=r[o]>>i)return!1;n-=e,o+=1}return!0}function u(t){if(i.test(t))return parseInt(t,16);if("0"===t[0]&&!isNaN(parseInt(t[1],10))){if(n.test(t))return parseInt(t,8);throw new Error(`ipaddr: cannot parse ${t} as octal`)}return parseInt(t,10)}function d(t,r){for(;t.length<r;)t=`0${t}`;return t}const c={};c.IPv4=function(){function t(t){if(4!==t.length)throw new Error("ipaddr: ipv4 octet count should be 4");let r,e;for(r=0;r<t.length;r++)if(!(0<=(e=t[r])&&e<=255))throw new Error("ipaddr: ipv4 octet should fit in 8 bits");this.octets=t}return t.prototype.SpecialRanges={unspecified:[[new t([0,0,0,0]),8]],broadcast:[[new t([255,255,255,255]),32]],multicast:[[new t([224,0,0,0]),4]],linkLocal:[[new t([169,254,0,0]),16]],loopback:[[new t([127,0,0,0]),8]],carrierGradeNat:[[new t([100,64,0,0]),10]],private:[[new t([10,0,0,0]),8],[new t([172,16,0,0]),12],[new t([192,168,0,0]),16]],reserved:[[new t([192,0,0,0]),24],[new t([192,0,2,0]),24],[new t([192,88,99,0]),24],[new t([198,51,100,0]),24],[new t([203,0,113,0]),24],[new t([240,0,0,0]),4]]},t.prototype.kind=function(){return"ipv4"},t.prototype.match=function(t,r){let e;if(void 0===r&&(t=(e=t)[0],r=e[1]),"ipv4"!==t.kind())throw new Error("ipaddr: cannot match ipv4 address with non-ipv4 one");return p(this.octets,t.octets,8,r)},t.prototype.prefixLengthFromSubnetMask=function(){let t=0,r=!1;const e={0:8,128:7,192:6,224:5,240:4,248:3,252:2,254:1,255:0};let n,i,o;for(n=3;n>=0;n-=1){if(!((i=this.octets[n])in e))return null;if(o=e[i],r&&0!==o)return null;8!==o&&(r=!0),t+=o}return 32-t},t.prototype.range=function(){return c.subnetMatch(this,this.SpecialRanges)},t.prototype.toByteArray=function(){return this.octets.slice(0)},t.prototype.toIPv4MappedAddress=function(){return c.IPv6.parse(`::ffff:${this.toString()}`)},t.prototype.toNormalizedString=function(){return this.toString()},t.prototype.toString=function(){return this.octets.join(".")},t}(),c.IPv4.broadcastAddressFromCIDR=function(t){try{const r=this.parseCIDR(t),e=r[0].toByteArray(),n=this.subnetMaskFromPrefixLength(r[1]).toByteArray(),i=[];let o=0;for(;o<4;)i.push(parseInt(e[o],10)|255^parseInt(n[o],10)),o++;return new this(i)}catch(t){throw new Error("ipaddr: the address does not have IPv4 CIDR format")}},c.IPv4.isIPv4=function(t){return null!==this.parser(t)},c.IPv4.isValid=function(t){try{return new this(this.parser(t)),!0}catch(t){return!1}},c.IPv4.isValidFourPartDecimal=function(t){return!(!c.IPv4.isValid(t)||!t.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/))},c.IPv4.networkAddressFromCIDR=function(t){let r,e,n,i,o;try{for(n=(r=this.parseCIDR(t))[0].toByteArray(),o=this.subnetMaskFromPrefixLength(r[1]).toByteArray(),i=[],e=0;e<4;)i.push(parseInt(n[e],10)&parseInt(o[e],10)),e++;return new this(i)}catch(t){throw new Error("ipaddr: the address does not have IPv4 CIDR format")}},c.IPv4.parse=function(t){const r=this.parser(t);if(null===r)throw new Error("ipaddr: string is not formatted like an IPv4 Address");return new this(r)},c.IPv4.parseCIDR=function(t){let r;if(r=t.match(/^(.+)\/(\d+)$/)){const t=parseInt(r[2]);if(t>=0&&t<=32){const e=[this.parse(r[1]),t];return Object.defineProperty(e,"toString",{value:function(){return this.join("/")}}),e}}throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range")},c.IPv4.parser=function(t){let r,n,i;if(r=t.match(e.fourOctet))return function(){const t=r.slice(1,6),e=[];for(let r=0;r<t.length;r++)n=t[r],e.push(u(n));return e}();if(r=t.match(e.longValue)){if((i=u(r[1]))>4294967295||i<0)throw new Error("ipaddr: address outside defined range");return function(){const t=[];let r;for(r=0;r<=24;r+=8)t.push(i>>r&255);return t}().reverse()}return(r=t.match(e.twoOctet))?function(){const t=r.slice(1,4),e=[];if((i=u(t[1]))>16777215||i<0)throw new Error("ipaddr: address outside defined range");return e.push(u(t[0])),e.push(i>>16&255),e.push(i>>8&255),e.push(255&i),e}():(r=t.match(e.threeOctet))?function(){const t=r.slice(1,5),e=[];if((i=u(t[2]))>65535||i<0)throw new Error("ipaddr: address outside defined range");return e.push(u(t[0])),e.push(u(t[1])),e.push(i>>8&255),e.push(255&i),e}():null},c.IPv4.subnetMaskFromPrefixLength=function(t){if((t=parseInt(t))<0||t>32)throw new Error("ipaddr: invalid IPv4 prefix length");const r=[0,0,0,0];let e=0;const n=Math.floor(t/8);for(;e<n;)r[e]=255,e++;return n<4&&(r[n]=Math.pow(2,t%8)-1<<8-t%8),new this(r)},c.IPv6=function(){function t(t,r){let e,n;if(16===t.length)for(this.parts=[],e=0;e<=14;e+=2)this.parts.push(t[e]<<8|t[e+1]);else{if(8!==t.length)throw new Error("ipaddr: ipv6 part count should be 8 or 16");this.parts=t}for(e=0;e<this.parts.length;e++)if(!(0<=(n=this.parts[e])&&n<=65535))throw new Error("ipaddr: ipv6 part should fit in 16 bits");r&&(this.zoneId=r)}return t.prototype.SpecialRanges={unspecified:[new t([0,0,0,0,0,0,0,0]),128],linkLocal:[new t([65152,0,0,0,0,0,0,0]),10],multicast:[new t([65280,0,0,0,0,0,0,0]),8],loopback:[new t([0,0,0,0,0,0,0,1]),128],uniqueLocal:[new t([64512,0,0,0,0,0,0,0]),7],ipv4Mapped:[new t([0,0,0,0,0,65535,0,0]),96],rfc6145:[new t([0,0,0,0,65535,0,0,0]),96],rfc6052:[new t([100,65435,0,0,0,0,0,0]),96],"6to4":[new t([8194,0,0,0,0,0,0,0]),16],teredo:[new t([8193,0,0,0,0,0,0,0]),32],reserved:[[new t([8193,3512,0,0,0,0,0,0]),32]]},t.prototype.isIPv4MappedAddress=function(){return"ipv4Mapped"===this.range()},t.prototype.kind=function(){return"ipv6"},t.prototype.match=function(t,r){let e;if(void 0===r&&(t=(e=t)[0],r=e[1]),"ipv6"!==t.kind())throw new Error("ipaddr: cannot match ipv6 address with non-ipv6 one");return p(this.parts,t.parts,16,r)},t.prototype.prefixLengthFromSubnetMask=function(){let t=0,r=!1;const e={0:16,32768:15,49152:14,57344:13,61440:12,63488:11,64512:10,65024:9,65280:8,65408:7,65472:6,65504:5,65520:4,65528:3,65532:2,65534:1,65535:0};let n,i;for(let o=7;o>=0;o-=1){if(!((n=this.parts[o])in e))return null;if(i=e[n],r&&0!==i)return null;16!==i&&(r=!0),t+=i}return 128-t},t.prototype.range=function(){return c.subnetMatch(this,this.SpecialRanges)},t.prototype.toByteArray=function(){let t;const r=[],e=this.parts;for(let n=0;n<e.length;n++)t=e[n],r.push(t>>8),r.push(255&t);return r},t.prototype.toFixedLengthString=function(){const t=function(){const t=[];for(let r=0;r<this.parts.length;r++)t.push(d(this.parts[r].toString(16),4));return t}.call(this).join(":");let r="";return this.zoneId&&(r=`%${this.zoneId}`),t+r},t.prototype.toIPv4Address=function(){if(!this.isIPv4MappedAddress())throw new Error("ipaddr: trying to convert a generic ipv6 address to ipv4");const t=this.parts.slice(-2),r=t[0],e=t[1];return new c.IPv4([r>>8,255&r,e>>8,255&e])},t.prototype.toNormalizedString=function(){const t=function(){const t=[];for(let r=0;r<this.parts.length;r++)t.push(this.parts[r].toString(16));return t}.call(this).join(":");let r="";return this.zoneId&&(r=`%${this.zoneId}`),t+r},t.prototype.toRFC5952String=function(){const t=/((^|:)(0(:|$)){2,})/g,r=this.toNormalizedString();let e,n=0,i=-1;for(;e=t.exec(r);)e[0].length>i&&(n=e.index,i=e[0].length);return i<0?r:`${r.substring(0,n)}::${r.substring(n+i)}`},t.prototype.toString=function(){return this.toNormalizedString().replace(/((^|:)(0(:|$))+)/,"::")},t}(),c.IPv6.isIPv6=function(t){return null!==this.parser(t)},c.IPv6.isValid=function(t){if("string"==typeof t&&-1===t.indexOf(":"))return!1;try{const r=this.parser(t);return new this(r.parts,r.zoneId),!0}catch(t){return!1}},c.IPv6.parse=function(t){const r=this.parser(t);if(null===r.parts)throw new Error("ipaddr: string is not formatted like an IPv6 Address");return new this(r.parts,r.zoneId)},c.IPv6.parseCIDR=function(t){let r,e,n;if((e=t.match(/^(.+)\/(\d+)$/))&&(r=parseInt(e[2]))>=0&&r<=128)return n=[this.parse(e[1]),r],Object.defineProperty(n,"toString",{value:function(){return this.join("/")}}),n;throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range")},c.IPv6.parser=function(t){let r,e,n,i,o,p;if(n=t.match(s.deprecatedTransitional))return this.parser(`::ffff:${n[1]}`);if(s.native.test(t))return a(t,8);if((n=t.match(s.transitional))&&(p=n[6]||"",(r=a(n[1].slice(0,-1)+p,6)).parts)){for(o=[parseInt(n[2]),parseInt(n[3]),parseInt(n[4]),parseInt(n[5])],e=0;e<o.length;e++)if(!(0<=(i=o[e])&&i<=255))return null;return r.parts.push(o[0]<<8|o[1]),r.parts.push(o[2]<<8|o[3]),{parts:r.parts,zoneId:r.zoneId}}return null},c.fromByteArray=function(t){const r=t.length;if(4===r)return new c.IPv4(t);if(16===r)return new c.IPv6(t);throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address")},c.isValid=function(t){return c.IPv6.isValid(t)||c.IPv4.isValid(t)},c.parse=function(t){if(c.IPv6.isValid(t))return c.IPv6.parse(t);if(c.IPv4.isValid(t))return c.IPv4.parse(t);throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format")},c.parseCIDR=function(t){try{return c.IPv6.parseCIDR(t)}catch(r){try{return c.IPv4.parseCIDR(t)}catch(t){throw new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format")}}},c.process=function(t){const r=this.parse(t);return"ipv6"===r.kind()&&r.isIPv4MappedAddress()?r.toIPv4Address():r},c.subnetMatch=function(t,r,e){let n,i,o,s;for(i in void 0!==e&&null!==e||(e="unicast"),r)if(Object.prototype.hasOwnProperty.call(r,i))for(!(o=r[i])[0]||o[0]instanceof Array||(o=[o]),n=0;n<o.length;n++)if(s=o[n],t.kind()===s[0].kind()&&t.match.apply(t,s))return i;return e},"undefined"!=typeof module&&module.exports?module.exports=c:t.ipaddr=c}(this)}("undefined"==typeof window?window={}:window);
\ No newline at end of file
+!function(t){!function(t){"use strict";const r="(0?\\d+|0x[a-f0-9]+)",e={fourOctet:new RegExp(`^${r}\\.${r}\\.${r}\\.${r}$`,"i"),threeOctet:new RegExp(`^${r}\\.${r}\\.${r}$`,"i"),twoOctet:new RegExp(`^${r}\\.${r}$`,"i"),longValue:new RegExp(`^${r}$`,"i")},n=new RegExp("^0[0-7]+$","i"),i=new RegExp("^0x[a-f0-9]+$","i"),o="(?:[0-9a-f]+::?)+",s={zoneIndex:new RegExp("%[0-9a-z]{1,}","i"),native:new RegExp(`^(::)?(${o})?([0-9a-f]+)?(::)?(%[0-9a-z]{1,})?$`,"i"),deprecatedTransitional:new RegExp(`^(?:::)(${r}\\.${r}\\.${r}\\.${r}(%[0-9a-z]{1,})?)$`,"i"),transitional:new RegExp(`^((?:${o})|(?:::)(?:${o})?)${r}\\.${r}\\.${r}\\.${r}(%[0-9a-z]{1,})?$`,"i")};function a(t,r){if(t.indexOf("::")!==t.lastIndexOf("::"))return null;let e,n,i=0,o=-1,a=(t.match(s.zoneIndex)||[])[0];for(a&&(a=a.substring(1),t=t.replace(/%.+$/,""));(o=t.indexOf(":",o+1))>=0;)i++;if("::"===t.substr(0,2)&&i--,"::"===t.substr(-2,2)&&i--,i>r)return null;for(n=r-i,e=":";n--;)e+="0:";return":"===(t=t.replace("::",e))[0]&&(t=t.slice(1)),":"===t[t.length-1]&&(t=t.slice(0,-1)),{parts:r=function(){const r=t.split(":"),e=[];for(let t=0;t<r.length;t++)e.push(parseInt(r[t],16));return e}(),zoneId:a}}function p(t,r,e,n){if(t.length!==r.length)throw new Error("ipaddr: cannot match CIDR for objects with different lengths");let i,o=0;for(;n>0;){if((i=e-n)<0&&(i=0),t[o]>>i!=r[o]>>i)return!1;n-=e,o+=1}return!0}function u(t){if(i.test(t))return parseInt(t,16);if("0"===t[0]&&!isNaN(parseInt(t[1],10))){if(n.test(t))return parseInt(t,8);throw new Error(`ipaddr: cannot parse ${t} as octal`)}return parseInt(t,10)}function d(t,r){for(;t.length<r;)t=`0${t}`;return t}const h={};h.IPv4=function(){function t(t){if(4!==t.length)throw new Error("ipaddr: ipv4 octet count should be 4");let r,e;for(r=0;r<t.length;r++)if(!(0<=(e=t[r])&&e<=255))throw new Error("ipaddr: ipv4 octet should fit in 8 bits");this.octets=t}return t.prototype.SpecialRanges={unspecified:[[new t([0,0,0,0]),8]],broadcast:[[new t([255,255,255,255]),32]],multicast:[[new t([224,0,0,0]),4]],linkLocal:[[new t([169,254,0,0]),16]],loopback:[[new t([127,0,0,0]),8]],carrierGradeNat:[[new t([100,64,0,0]),10]],private:[[new t([10,0,0,0]),8],[new t([172,16,0,0]),12],[new t([192,168,0,0]),16]],reserved:[[new t([192,0,0,0]),24],[new t([192,0,2,0]),24],[new t([192,88,99,0]),24],[new t([198,51,100,0]),24],[new t([203,0,113,0]),24],[new t([240,0,0,0]),4]]},t.prototype.kind=function(){return"ipv4"},t.prototype.match=function(t,r){let e;if(void 0===r&&(t=(e=t)[0],r=e[1]),"ipv4"!==t.kind())throw new Error("ipaddr: cannot match ipv4 address with non-ipv4 one");return p(this.octets,t.octets,8,r)},t.prototype.prefixLengthFromSubnetMask=function(){let t=0,r=!1;const e={0:8,128:7,192:6,224:5,240:4,248:3,252:2,254:1,255:0};let n,i,o;for(n=3;n>=0;n-=1){if(!((i=this.octets[n])in e))return null;if(o=e[i],r&&0!==o)return null;8!==o&&(r=!0),t+=o}return 32-t},t.prototype.range=function(){return h.subnetMatch(this,this.SpecialRanges)},t.prototype.toByteArray=function(){return this.octets.slice(0)},t.prototype.toIPv4MappedAddress=function(){return h.IPv6.parse(`::ffff:${this.toString()}`)},t.prototype.toNormalizedString=function(){return this.toString()},t.prototype.toString=function(){return this.octets.join(".")},t}(),h.IPv4.broadcastAddressFromCIDR=function(t){try{const r=this.parseCIDR(t),e=r[0].toByteArray(),n=this.subnetMaskFromPrefixLength(r[1]).toByteArray(),i=[];let o=0;for(;o<4;)i.push(parseInt(e[o],10)|255^parseInt(n[o],10)),o++;return new this(i)}catch(t){throw new Error("ipaddr: the address does not have IPv4 CIDR format")}},h.IPv4.isIPv4=function(t){return null!==this.parser(t)},h.IPv4.isValid=function(t){try{return new this(this.parser(t)),!0}catch(t){return!1}},h.IPv4.isValidFourPartDecimal=function(t){return!(!h.IPv4.isValid(t)||!t.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/))},h.IPv4.networkAddressFromCIDR=function(t){let r,e,n,i,o;try{for(n=(r=this.parseCIDR(t))[0].toByteArray(),o=this.subnetMaskFromPrefixLength(r[1]).toByteArray(),i=[],e=0;e<4;)i.push(parseInt(n[e],10)&parseInt(o[e],10)),e++;return new this(i)}catch(t){throw new Error("ipaddr: the address does not have IPv4 CIDR format")}},h.IPv4.parse=function(t){const r=this.parser(t);if(null===r)throw new Error("ipaddr: string is not formatted like an IPv4 Address");return new this(r)},h.IPv4.parseCIDR=function(t){let r;if(r=t.match(/^(.+)\/(\d+)$/)){const t=parseInt(r[2]);if(t>=0&&t<=32){const e=[this.parse(r[1]),t];return Object.defineProperty(e,"toString",{value:function(){return this.join("/")}}),e}}throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range")},h.IPv4.parser=function(t){let r,n,i;if(r=t.match(e.fourOctet))return function(){const t=r.slice(1,6),e=[];for(let r=0;r<t.length;r++)n=t[r],e.push(u(n));return e}();if(r=t.match(e.longValue)){if((i=u(r[1]))>4294967295||i<0)throw new Error("ipaddr: address outside defined range");return function(){const t=[];let r;for(r=0;r<=24;r+=8)t.push(i>>r&255);return t}().reverse()}return(r=t.match(e.twoOctet))?function(){const t=r.slice(1,4),e=[];if((i=u(t[1]))>16777215||i<0)throw new Error("ipaddr: address outside defined range");return e.push(u(t[0])),e.push(i>>16&255),e.push(i>>8&255),e.push(255&i),e}():(r=t.match(e.threeOctet))?function(){const t=r.slice(1,5),e=[];if((i=u(t[2]))>65535||i<0)throw new Error("ipaddr: address outside defined range");return e.push(u(t[0])),e.push(u(t[1])),e.push(i>>8&255),e.push(255&i),e}():null},h.IPv4.subnetMaskFromPrefixLength=function(t){if((t=parseInt(t))<0||t>32)throw new Error("ipaddr: invalid IPv4 prefix length");const r=[0,0,0,0];let e=0;const n=Math.floor(t/8);for(;e<n;)r[e]=255,e++;return n<4&&(r[n]=Math.pow(2,t%8)-1<<8-t%8),new this(r)},h.IPv6=function(){function t(t,r){let e,n;if(16===t.length)for(this.parts=[],e=0;e<=14;e+=2)this.parts.push(t[e]<<8|t[e+1]);else{if(8!==t.length)throw new Error("ipaddr: ipv6 part count should be 8 or 16");this.parts=t}for(e=0;e<this.parts.length;e++)if(!(0<=(n=this.parts[e])&&n<=65535))throw new Error("ipaddr: ipv6 part should fit in 16 bits");r&&(this.zoneId=r)}return t.prototype.SpecialRanges={unspecified:[new t([0,0,0,0,0,0,0,0]),128],linkLocal:[new t([65152,0,0,0,0,0,0,0]),10],multicast:[new t([65280,0,0,0,0,0,0,0]),8],loopback:[new t([0,0,0,0,0,0,0,1]),128],uniqueLocal:[new t([64512,0,0,0,0,0,0,0]),7],ipv4Mapped:[new t([0,0,0,0,0,65535,0,0]),96],rfc6145:[new t([0,0,0,0,65535,0,0,0]),96],rfc6052:[new t([100,65435,0,0,0,0,0,0]),96],"6to4":[new t([8194,0,0,0,0,0,0,0]),16],teredo:[new t([8193,0,0,0,0,0,0,0]),32],reserved:[[new t([8193,3512,0,0,0,0,0,0]),32]]},t.prototype.isIPv4MappedAddress=function(){return"ipv4Mapped"===this.range()},t.prototype.kind=function(){return"ipv6"},t.prototype.match=function(t,r){let e;if(void 0===r&&(t=(e=t)[0],r=e[1]),"ipv6"!==t.kind())throw new Error("ipaddr: cannot match ipv6 address with non-ipv6 one");return p(this.parts,t.parts,16,r)},t.prototype.prefixLengthFromSubnetMask=function(){let t=0,r=!1;const e={0:16,32768:15,49152:14,57344:13,61440:12,63488:11,64512:10,65024:9,65280:8,65408:7,65472:6,65504:5,65520:4,65528:3,65532:2,65534:1,65535:0};let n,i;for(let o=7;o>=0;o-=1){if(!((n=this.parts[o])in e))return null;if(i=e[n],r&&0!==i)return null;16!==i&&(r=!0),t+=i}return 128-t},t.prototype.range=function(){return h.subnetMatch(this,this.SpecialRanges)},t.prototype.toByteArray=function(){let t;const r=[],e=this.parts;for(let n=0;n<e.length;n++)t=e[n],r.push(t>>8),r.push(255&t);return r},t.prototype.toFixedLengthString=function(){const t=function(){const t=[];for(let r=0;r<this.parts.length;r++)t.push(d(this.parts[r].toString(16),4));return t}.call(this).join(":");let r="";return this.zoneId&&(r=`%${this.zoneId}`),t+r},t.prototype.toIPv4Address=function(){if(!this.isIPv4MappedAddress())throw new Error("ipaddr: trying to convert a generic ipv6 address to ipv4");const t=this.parts.slice(-2),r=t[0],e=t[1];return new h.IPv4([r>>8,255&r,e>>8,255&e])},t.prototype.toNormalizedString=function(){const t=function(){const t=[];for(let r=0;r<this.parts.length;r++)t.push(this.parts[r].toString(16));return t}.call(this).join(":");let r="";return this.zoneId&&(r=`%${this.zoneId}`),t+r},t.prototype.toRFC5952String=function(){const t=/((^|:)(0(:|$)){2,})/g,r=this.toNormalizedString();let e,n=0,i=-1;for(;e=t.exec(r);)e[0].length>i&&(n=e.index,i=e[0].length);return i<0?r:`${r.substring(0,n)}::${r.substring(n+i)}`},t.prototype.toString=function(){return this.toNormalizedString().replace(/((^|:)(0(:|$))+)/,"::")},t}(),h.IPv6.broadcastAddressFromCIDR=function(t){try{const r=this.parseCIDR(t),e=r[0].toByteArray(),n=this.subnetMaskFromPrefixLength(r[1]).toByteArray(),i=[];let o=0;for(;o<16;)i.push(parseInt(e[o],10)|255^parseInt(n[o],10)),o++;return new this(i)}catch(t){throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${t})`)}},h.IPv6.isIPv6=function(t){return null!==this.parser(t)},h.IPv6.isValid=function(t){if("string"==typeof t&&-1===t.indexOf(":"))return!1;try{const r=this.parser(t);return new this(r.parts,r.zoneId),!0}catch(t){return!1}},h.IPv6.networkAddressFromCIDR=function(t){let r,e,n,i,o;try{for(n=(r=this.parseCIDR(t))[0].toByteArray(),o=this.subnetMaskFromPrefixLength(r[1]).toByteArray(),i=[],e=0;e<16;)i.push(parseInt(n[e],10)&parseInt(o[e],10)),e++;return new this(i)}catch(t){throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${t})`)}},h.IPv6.parse=function(t){const r=this.parser(t);if(null===r.parts)throw new Error("ipaddr: string is not formatted like an IPv6 Address");return new this(r.parts,r.zoneId)},h.IPv6.parseCIDR=function(t){let r,e,n;if((e=t.match(/^(.+)\/(\d+)$/))&&(r=parseInt(e[2]))>=0&&r<=128)return n=[this.parse(e[1]),r],Object.defineProperty(n,"toString",{value:function(){return this.join("/")}}),n;throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range")},h.IPv6.parser=function(t){let r,e,n,i,o,p;if(n=t.match(s.deprecatedTransitional))return this.parser(`::ffff:${n[1]}`);if(s.native.test(t))return a(t,8);if((n=t.match(s.transitional))&&(p=n[6]||"",(r=a(n[1].slice(0,-1)+p,6)).parts)){for(o=[parseInt(n[2]),parseInt(n[3]),parseInt(n[4]),parseInt(n[5])],e=0;e<o.length;e++)if(!(0<=(i=o[e])&&i<=255))return null;return r.parts.push(o[0]<<8|o[1]),r.parts.push(o[2]<<8|o[3]),{parts:r.parts,zoneId:r.zoneId}}return null},h.IPv6.subnetMaskFromPrefixLength=function(t){if((t=parseInt(t))<0||t>128)throw new Error("ipaddr: invalid IPv6 prefix length");const r=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];let e=0;const n=Math.floor(t/8);for(;e<n;)r[e]=255,e++;return n<16&&(r[n]=Math.pow(2,t%8)-1<<8-t%8),new this(r)},h.fromByteArray=function(t){const r=t.length;if(4===r)return new h.IPv4(t);if(16===r)return new h.IPv6(t);throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address")},h.isValid=function(t){return h.IPv6.isValid(t)||h.IPv4.isValid(t)},h.parse=function(t){if(h.IPv6.isValid(t))return h.IPv6.parse(t);if(h.IPv4.isValid(t))return h.IPv4.parse(t);throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format")},h.parseCIDR=function(t){try{return h.IPv6.parseCIDR(t)}catch(r){try{return h.IPv4.parseCIDR(t)}catch(t){throw new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format")}}},h.process=function(t){const r=this.parse(t);return"ipv6"===r.kind()&&r.isIPv4MappedAddress()?r.toIPv4Address():r},h.subnetMatch=function(t,r,e){let n,i,o,s;for(i in void 0!==e&&null!==e||(e="unicast"),r)if(Object.prototype.hasOwnProperty.call(r,i))for(!(o=r[i])[0]||o[0]instanceof Array||(o=[o]),n=0;n<o.length;n++)if(s=o[n],t.kind()===s[0].kind()&&t.match.apply(t,s))return i;return e},"undefined"!=typeof module&&module.exports?module.exports=h:t.ipaddr=h}(this)}("undefined"==typeof window?window={}:window);
\ No newline at end of file
diff --git a/lib/ipaddr.js b/lib/ipaddr.js
index c58094b..efdd8fb 100644
--- a/lib/ipaddr.js
+++ b/lib/ipaddr.js
@@ -741,6 +741,26 @@
 
     })();
 
+    // A utility function to return broadcast address given the IPv6 interface and prefix length in CIDR notation
+    ipaddr.IPv6.broadcastAddressFromCIDR = function (string) {
+        try {
+            const cidr = this.parseCIDR(string);
+            const ipInterfaceOctets = cidr[0].toByteArray();
+            const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
+            const octets = [];
+            let i = 0;
+            while (i < 16) {
+                // Broadcast address is bitwise OR between ip interface and inverted mask
+                octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255);
+                i++;
+            }
+
+            return new this(octets);
+        } catch (e) {
+            throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
+        }
+    };
+
     // Checks if a given string is formatted like IPv6 address.
     ipaddr.IPv6.isIPv6 = function (string) {
         return this.parser(string) !== null;
@@ -764,6 +784,28 @@
         }
     };
 
+    // A utility function to return network address given the IPv6 interface and prefix length in CIDR notation
+    ipaddr.IPv6.networkAddressFromCIDR = function (string) {
+        let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets;
+
+        try {
+            cidr = this.parseCIDR(string);
+            ipInterfaceOctets = cidr[0].toByteArray();
+            subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
+            octets = [];
+            i = 0;
+            while (i < 16) {
+                // Network address is bitwise AND between ip interface and mask
+                octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10));
+                i++;
+            }
+
+            return new this(octets);
+        } catch (e) {
+            throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
+        }
+    };
+
     // Tries to parse and validate a string with IPv6 address.
     // Throws an error if it fails.
     ipaddr.IPv6.parse = function (string) {
@@ -834,6 +876,29 @@
         return null;
     };
 
+    // A utility function to return subnet mask in IPv6 format given the prefix length
+    ipaddr.IPv6.subnetMaskFromPrefixLength = function (prefix) {
+        prefix = parseInt(prefix);
+        if (prefix < 0 || prefix > 128) {
+            throw new Error('ipaddr: invalid IPv6 prefix length');
+        }
+
+        const octets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+        let j = 0;
+        const filledOctetCount = Math.floor(prefix / 8);
+
+        while (j < filledOctetCount) {
+            octets[j] = 255;
+            j++;
+        }
+
+        if (filledOctetCount < 16) {
+            octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8);
+        }
+
+        return new this(octets);
+    };
+
     // Try to parse an array in network order (MSB first) for IPv4 and IPv6
     ipaddr.fromByteArray = function (bytes) {
         const length = bytes.length;
@@ -852,6 +917,7 @@
         return ipaddr.IPv6.isValid(string) || ipaddr.IPv4.isValid(string);
     };
 
+
     // Attempts to parse an IP Address, first through IPv6 then IPv4.
     // Throws an error if it could not be parsed.
     ipaddr.parse = function (string) {
diff --git a/lib/ipaddr.js.d.ts b/lib/ipaddr.js.d.ts
index 852f0ec..fc588bd 100644
--- a/lib/ipaddr.js.d.ts
+++ b/lib/ipaddr.js.d.ts
@@ -46,6 +46,7 @@ declare module "ipaddr.js" {
             static broadcastAddressFromCIDR(addr: string): IPv6;
             static isIPv6(addr: string): boolean;
             static isValid(addr: string): boolean;
+            static networkAddressFromCIDR(addr: string): IPv6;
             static parse(addr: string): IPv6;
             static parseCIDR(addr: string): [IPv6, number];
             static subnetMaskFromPrefixLength(prefix: number): IPv6;
diff --git a/package.json b/package.json
index 749b2af..0364a2f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "ipaddr.js",
   "description": "A library for manipulating IPv4 and IPv6 addresses in JavaScript.",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "author": "whitequark <whitequark@whitequark.org>",
   "directories": {
     "lib": "./lib"


Test patch:
diff --git a/test/ipaddr.test.js b/test/ipaddr.test.js
index dc05b0f..d7de7d6 100644
--- a/test/ipaddr.test.js
+++ b/test/ipaddr.test.js
@@ -627,6 +627,23 @@ describe('ipaddr', () => {
         done();
     })
 
+    it('subnetMaskFromPrefixLength returns correct IPv6 subnet mask given prefix length', (done) => {
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(128), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(112), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(96),  'ffff:ffff:ffff:ffff:ffff:ffff::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(72),  'ffff:ffff:ffff:ffff:ff00::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(64),  'ffff:ffff:ffff:ffff::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(48),  'ffff:ffff:ffff::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(32),  'ffff:ffff::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(16),  'ffff::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(8),   'ff00::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(4),   'f000::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(2),   'c000::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(1),   '8000::');
+        assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(0),   '::');
+        done();
+    })
+
     it('broadcastAddressFromCIDR returns correct IPv4 broadcast address', (done) => {
         assert.equal(ipaddr.IPv4.broadcastAddressFromCIDR('172.0.0.1/24'), '172.0.0.255');
         assert.equal(ipaddr.IPv4.broadcastAddressFromCIDR('172.0.0.1/26'), '172.0.0.63');
@@ -638,4 +655,31 @@ describe('ipaddr', () => {
         assert.equal(ipaddr.IPv4.networkAddressFromCIDR('172.0.0.1/5'), '168.0.0.0');
         done();
     })
+
+    it('networkAddressFromCIDR returns correct IPv6 network address', (done) => {
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('::/0'),                  '::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f53a::1:1/64'), '2001:db8:f53a::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f53b::1:1/48'), '2001:db8:f53b::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f531::1:1/44'), '2001:db8:f530::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f500::1/40'),   '2001:db8:f500::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f500::1%z/40'), '2001:db8:f500::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db9:f500::1/40'),   '2001:db9:f500::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db9:f500::1%z/40'), '2001:db9:f500::');
+        assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f53a::1/128'),  '2001:db8:f53a::1');
+        done();
+    })
+
+    it('broadcastAddressFromCIDR returns correct IPv6 broadcast address', (done) => {
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('::/0'),                  'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f53a::1:1/64'), '2001:db8:f53a::ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f53b::1:1/48'), '2001:db8:f53b:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f531::1:1/44'), '2001:db8:f53f:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f500::1/40'),   '2001:db8:f5ff:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f500::1%z/40'), '2001:db8:f5ff:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db9:f500::1/40'),   '2001:db9:f5ff:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db9:f500::1%z/40'), '2001:db9:f5ff:ffff:ffff:ffff:ffff:ffff');
+        assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f53a::1/128'),  '2001:db8:f53a::1');
+        done();
+    })
+
 })