Skip to content

Commit

Permalink
Merge pull request #331 from Comcast/topic/mac
Browse files Browse the repository at this point in the history
Add MacAddress
  • Loading branch information
mpilquist authored Oct 21, 2021
2 parents 13cec61 + 4ba8a97 commit 98c5826
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
version = "3.0.6"
maxColumn: 120
fileOverride {
"glob:**/shared/src/main/scala-3/**" {
runner.dialect = scala3
}
}
125 changes: 125 additions & 0 deletions shared/src/main/scala/com/comcast/ip4s/MacAddress.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2018 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.comcast.ip4s

import scala.util.control.NonFatal
import cats.{Order, Show}

/** 6-byte MAC address. */
final class MacAddress private (private val bytes: Array[Byte]) extends Ordered[MacAddress] with Serializable {
def toBytes: Array[Byte] = bytes.clone

def toLong: Long = {
val bs = bytes
var result = 0L
for (i <- 0 until bs.size) {
result = (result << 8) | (0x0ff & bs(i))
}
result
}

override def compare(that: MacAddress): Int = {
var i, result = 0
val tb = that.bytes
val sz = bytes.length
while (i < sz && result == 0) {
result = Integer.compare(bytes(i) & 0xff, tb(i) & 0xff)
i += 1
}
result
}

override def equals(other: Any): Boolean = other match {
case that: MacAddress => java.util.Arrays.equals(bytes, that.bytes)
case _ => false
}

override def hashCode: Int = java.util.Arrays.hashCode(bytes)

override def toString: String =
bytes.map(b => f"${0xff & b}%02x").mkString("", ":", "")
}

object MacAddress {

/** Constructs a `MacAddress` from a 6-element byte array. Returns `Some` when array is exactly 6-bytes and `None`
* otherwise.
*/
def fromBytes(bytes: Array[Byte]): Option[MacAddress] = {
if (bytes.length == 6) Some(new MacAddress(bytes))
else None
}

/** Constructs a `MacAddress` from the specified 6 bytes.
*
* Each byte is represented as an `Int` to avoid having to manually call `.toByte` on each value -- the `toByte` call
* is done inside this function.
*/
def fromBytes(
b0: Int,
b1: Int,
b2: Int,
b3: Int,
b4: Int,
b5: Int
): MacAddress = {
val bytes = new Array[Byte](6)
bytes(0) = b0.toByte
bytes(1) = b1.toByte
bytes(2) = b2.toByte
bytes(3) = b3.toByte
bytes(4) = b4.toByte
bytes(5) = b5.toByte
new MacAddress(bytes)
}

/** Constructs a `MacAddress` from a `Long`, using the lower 48-bits. */
def fromLong(value: Long): MacAddress = {
val bytes = new Array[Byte](6)
var rem = value
for (i <- 5 to 0 by -1) {
bytes(i) = (rem & 0x0ff).toByte
rem = rem >> 8
}
new MacAddress(bytes)
}

/** Parses a `MacAddress` from a string, returning `None` if the string is not a valid mac. */
def fromString(value: String): Option[MacAddress] = {
val trimmed = value.trim
val fields = trimmed.split(':')
if (fields.length == 6) {
val result = new Array[Byte](6)
var i = 0
while (i < result.length) {
val field = fields(i)
if (field.size == 2) {
try {
result(i) = (0xff & Integer.parseInt(field, 16)).toByte
i += 1
} catch {
case NonFatal(_) => return None
}
} else return None
}
Some(new MacAddress(result))
} else None
}

implicit val order: Order[MacAddress] = Order.fromComparable[MacAddress]
implicit val show: Show[MacAddress] = Show.fromToString[MacAddress]
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,10 @@ object Arbitraries {
}

implicit def idnArbitrary: Arbitrary[IDN] = Arbitrary(idnGenerator)

val macAddressGenerator: Gen[MacAddress] = for {
bytes <- Gen.listOfN(6, Arbitrary.arbitrary[Byte])
} yield MacAddress.fromBytes(bytes.toArray).get

implicit val macAddressArbitrary: Arbitrary[MacAddress] = Arbitrary(macAddressGenerator)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2018 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.comcast.ip4s

import org.scalacheck.{Arbitrary, Gen, Prop}
import Prop.forAll
import Arbitraries._

class MacAddressTest extends BaseTestSuite {
test("roundtrip through string") {
forAll(Gen.listOfN(6, Arbitrary.arbitrary[Byte])) { bytesList =>
if (bytesList.size == 6) {
val bytes = bytesList.toArray
val addr = MacAddress.fromBytes(bytes).get
assertEquals(MacAddress.fromString(addr.toString), Some(addr))
}
}
}

test("support ordering") {
forAll { (left: MacAddress, right: MacAddress) =>
val longCompare = left.toLong.compare(right.toLong)
val result = Ordering[MacAddress].compare(left, right)
assertEquals(result, longCompare)
}
}
}

0 comments on commit 98c5826

Please sign in to comment.