Skip to content

Commit 3e93afc

Browse files
HT154The Pkl Team (automation)
and
The Pkl Team (automation)
authored
[pkl.experimental.net] Add MAC->EUI-64 calculation (#5)
* expose `add` on IPv*Address types * remove `const` modifier from module methods where not necessary Co-authored-by: The Pkl Team (automation) <pkl-oss@groups.apple.com>
1 parent 2a49a6f commit 3e93afc

File tree

3 files changed

+79
-16
lines changed

3 files changed

+79
-16
lines changed

packages/pkl.experimental.net/PklProject

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
amends "../basePklProject.pkl"
1818

1919
package {
20-
version = "1.0.0"
20+
version = "1.1.0"
2121
}

packages/pkl.experimental.net/net.pkl

+50-15
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ typealias IPv4CIDRString = String(matches(Regex(#"\#(net.ipv4String)/\d{1,2}"#))
6868
typealias IPv6CIDRString = String(matches(Regex(#"\#(net.ipv6String)/\d{1,3}"#)))
6969

7070
/// Creates an [IPAddress] from an [IPAddressString].
71-
const function IP(ip: IPAddressString): IPAddress =
71+
function IP(ip: IPAddressString): IPAddress =
7272
if (ip is IPv6AddressString) IPv6Address(ip)
7373
else if (ip is IPv4AddressString) IPv4Address(ip)
7474
else throw("Invalid IP: \(ip)")
7575

7676
/// Creates an [IPv4Address] from an [IPv4AddressString].
77-
const function IPv4Address(ip: IPv4AddressString): IPv4Address = new {
77+
function IPv4Address(ip: IPv4AddressString): IPv4Address = new {
7878
local parts = ip.split(".")
7979
repr = parts[0].toInt().shl(24)
8080
.or(parts[1].toInt().shl(16))
@@ -98,7 +98,10 @@ class IPv4Address {
9898
function reverse(): String = new IPv4Network { base = self; prefix = self.bitWidth }.reverse()
9999

100100
/// return the ip address immediately after this one
101-
function next(): IPv4Address = new { repr = self.repr + 1 }
101+
function next(): IPv4Address = add(1)
102+
103+
/// return the ip address [n] after this one
104+
function add(n: UInt32): IPv4Address = new { repr = self.repr + n }
102105

103106
function toString(): IPv4AddressString = new Listing<String> {
104107
repr.ushr(24).and(math.maxUInt8).toString()
@@ -109,7 +112,7 @@ class IPv4Address {
109112
}
110113

111114
/// Creates an [IPv6Address] from an [IPv6AddressString].
112-
const function IPv6Address(ip: IPv6AddressString): IPv6Address =
115+
function IPv6Address(ip: IPv6AddressString): IPv6Address =
113116
let (_ip = expandIPv6AddressString(ip).toLowerCase().split(":"))
114117
new {
115118
repr = u128.UInt128(
@@ -144,7 +147,10 @@ class IPv6Address {
144147
function reverse(): String = new IPv6Network { base = self; prefix = self.bitWidth }.reverse()
145148

146149
/// return the ip address immediately after this one
147-
function next(): IPv6Address = new { repr = self.repr.add(u128.one) }
150+
function next(): IPv6Address = add(1)
151+
152+
/// return the ip address [n] after this one
153+
function add(n: UInt32): IPv6Address = new { repr = self.repr.add(u128.UInt128(0, 0, 0, n)) }
148154

149155
function toString(): IPv6AddressString = expandIPv6AddressString(new Listing {
150156
repr.hihi.ushr(16).toRadixString(16)
@@ -159,13 +165,13 @@ class IPv6Address {
159165
}
160166

161167
/// Creates an [IPNetwork] from an IPv4 or IPv6 CIDR block string
162-
const function IPNetwork(cidr: String): IPNetwork =
168+
function IPNetwork(cidr: String): IPNetwork =
163169
if (cidr.split("/").first is IPv4AddressString) IPv4Network(cidr)
164170
else if (cidr.split("/").first is IPv6AddressString) IPv6Network(cidr)
165171
else throw("Invalid network CIDR: \(cidr)")
166172

167173
/// Creates an [IPv4Network] from an IPv4 CIDR block string
168-
const function IPv4Network(cidr: String): IPv4Network = new {
174+
function IPv4Network(cidr: String): IPv4Network = new {
169175
base = IPv4Address(cidr.split("/").first)
170176
prefix = cidr.split("/").last.toInt()
171177
}
@@ -209,14 +215,14 @@ class IPv4Network {
209215
}
210216

211217
/// Produces a listing of IPv4 addresses between [start] and [end], inclusive.
212-
const function IPv4Range(start: IPv4Address, end: IPv4Address): Listing<IPv4Address> = new {
218+
function IPv4Range(start: IPv4Address, end: IPv4Address): Listing<IPv4Address> = new {
213219
for (ipu in IntSeq(start.repr, end.repr)) {
214220
new { repr = ipu }
215221
}
216222
}
217223

218224
/// Creates an [IPv6Network] from an IPv6 CIDR block string
219-
const function IPv6Network(cidr: String): IPv6Network = new {
225+
function IPv6Network(cidr: String): IPv6Network = new {
220226
base = IPv6Address(cidr.split("/").first)
221227
prefix = cidr.split("/").last.toInt()
222228
}
@@ -257,7 +263,7 @@ class IPv6Network {
257263
}
258264

259265
/// Produces a listing of IPv6 addresses between [start] and [end], inclusive.
260-
const function IPv6Range(start: IPv6Address, end: IPv6Address): Listing<IPv6Address> = new {
266+
function IPv6Range(start: IPv6Address, end: IPv6Address): Listing<IPv6Address> = new {
261267
for (ipu in start.repr.seq(end.repr)) {
262268
new { repr = ipu }
263269
}
@@ -284,22 +290,51 @@ const function compressIPv6AddressString(ip: IPv6AddressString): IPv6AddressStri
284290
).join(":"))
285291
trimmed.replaceFirst(Regex("(:|^)(0:)+0(:|$)"), "::")
286292

293+
function MACAddress(mac: MACAddressString): MACAddress = new {
294+
repr = mac.split(Regex("[.:-]")).map((octet) -> parseHexOctet(octet))
295+
}
296+
297+
class MACAddress {
298+
hidden repr: List<UInt8>(length == 6)
299+
300+
local self = this
301+
302+
function toString(): MACAddressString = repr.map((octet) -> octet.toRadixString(16).padStart(2, "0")).join(":")
303+
304+
function eui64(addr: IPv6Address): IPv6Address = new {
305+
repr = u128.UInt128(
306+
addr.repr.hihi,
307+
addr.repr.hilo,
308+
uint32FromBytes(self.repr[0].xor(0x02), self.repr[1], self.repr[2], 0xff),
309+
uint32FromBytes(0xfe, self.repr[3], self.repr[4], self.repr[5])
310+
)
311+
}
312+
}
313+
287314
/// parseHex tranforms a single hexadecimal character into its unsigned integer representation.
288-
const function parseHex(digit: Char): UInt8 =
315+
function parseHex(digit: Char): UInt8 =
289316
let (d = digit.toLowerCase())
290317
"0123456789abcdef".chars.findIndexOrNull((it) -> it == d) ??
291318
throw("Unrecognized hex digit: \(d)")
292319

320+
/// parseHexOctet tranforms a two hexadecimal characters into its unsigned integer representation.
321+
function parseHexOctet(octet: String(length == 2)): UInt8 = byteLut[octet.toLowerCase()]
322+
293323
/// parseHex32 transforms an 8 character hexidecimal string into its UInt32 representation.
294-
const function parseHex32(s: String(this.length == 8)): UInt32 =
324+
function parseHex32(s: String(length == 8)): UInt32 =
295325
IntSeq(0, 7)
296326
.step(2)
297327
.map((it) -> s.substring(it, it + 2))
298-
.fold(0, (acc, it) -> acc.shl(8) + byteLut[it.toLowerCase()])
328+
.fold(0, (acc, it) -> acc.shl(8) + parseHexOctet(it))
299329

330+
/// byteLut is a lookup table mapping a string of two lowercase hex digits (zero-padded) to the UInt8 value.
300331
const local byteLut = IntSeq(0, 255).map((it) -> it).toMap((it) -> it.toRadixString(16).padStart(2, "0"), (it) -> it)
301332

302333
/// mask32Hi generates a mask of 1s in the top [prefix] bits of a [UInt32].
303-
const function mask32Hi(prefix: UInt(this.isBetween(0, 32))): UInt32 = math.maxUInt32.ushr(32-prefix).shl(32-prefix)
334+
const function mask32Hi(prefix: UInt(isBetween(0, 32))): UInt32 = math.maxUInt32.ushr(32-prefix).shl(32-prefix)
304335
/// mask32Lo generates a mask of 1s in the bottom [suffix] bits of a [UInt32].
305-
const function mask32Lo(suffix: UInt(this.isBetween(0, 32))): UInt32 = math.maxUInt32.ushr(32-suffix)
336+
const function mask32Lo(suffix: UInt(isBetween(0, 32))): UInt32 = math.maxUInt32.ushr(32-suffix)
337+
338+
/// uint32FromBytes constructs a [UInt32] from four [UInt8] values.
339+
const function uint32FromBytes(hihi: UInt8, hilo: UInt8, lohi: UInt8, lolo: UInt8): UInt32 =
340+
hihi.shl(24).or(hilo.shl(16)).or(lohi.shl(8)).or(lolo)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
module pkl.experimental.net.tests.net
17+
18+
amends "pkl:test"
19+
20+
import "../net.pkl"
21+
22+
local baseIP = net.IPv6Address("::")
23+
24+
facts {
25+
["MACAddress"] {
26+
net.MACAddress("34:73:5A:FA:AF:22").eui64(baseIP).toString() == "0000:0000:0000:0000:3673:5aff:fefa:af22"
27+
}
28+
}

0 commit comments

Comments
 (0)