* Expand `::` and optional trailing dotted-decimal so an IPv6 address is * represented as exactly 8 hex groups. Returns null if expansion is not * well-formed (the caller has already validated with isIP, so this is * defensive).
(addr: string)
| 131 | * defensive). |
| 132 | */ |
| 133 | function expandIPv6Groups(addr: string): number[] | null { |
| 134 | // Handle trailing dotted-decimal IPv4 (e.g. ::ffff:169.254.169.254). |
| 135 | // Replace it with its two hex groups so the rest of the expansion is uniform. |
| 136 | let tailHextets: number[] = [] |
| 137 | if (addr.includes('.')) { |
| 138 | const lastColon = addr.lastIndexOf(':') |
| 139 | const v4 = addr.slice(lastColon + 1) |
| 140 | addr = addr.slice(0, lastColon) |
| 141 | const octets = v4.split('.').map(Number) |
| 142 | if ( |
| 143 | octets.length !== 4 || |
| 144 | octets.some(n => !Number.isInteger(n) || n < 0 || n > 255) |
| 145 | ) { |
| 146 | return null |
| 147 | } |
| 148 | tailHextets = [ |
| 149 | (octets[0]! << 8) | octets[1]!, |
| 150 | (octets[2]! << 8) | octets[3]!, |
| 151 | ] |
| 152 | } |
| 153 | |
| 154 | // Expand `::` (at most one) into the right number of zero groups. |
| 155 | const dbl = addr.indexOf('::') |
| 156 | let head: string[] |
| 157 | let tail: string[] |
| 158 | if (dbl === -1) { |
| 159 | head = addr.split(':') |
| 160 | tail = [] |
| 161 | } else { |
| 162 | const headStr = addr.slice(0, dbl) |
| 163 | const tailStr = addr.slice(dbl + 2) |
| 164 | head = headStr === '' ? [] : headStr.split(':') |
| 165 | tail = tailStr === '' ? [] : tailStr.split(':') |
| 166 | } |
| 167 | |
| 168 | const target = 8 - tailHextets.length |
| 169 | const fill = target - head.length - tail.length |
| 170 | if (fill < 0) return null |
| 171 | |
| 172 | const hex = [...head, ...new Array<string>(fill).fill('0'), ...tail] |
| 173 | const nums = hex.map(h => parseInt(h, 16)) |
| 174 | if (nums.some(n => Number.isNaN(n) || n < 0 || n > 0xffff)) { |
| 175 | return null |
| 176 | } |
| 177 | nums.push(...tailHextets) |
| 178 | return nums.length === 8 ? nums : null |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * Extract the embedded IPv4 address from an IPv4-mapped IPv6 address |
no test coverage detected