summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2021-01-25 23:59:45 +0100
committerSven Gothel <[email protected]>2021-01-25 23:59:45 +0100
commited76a94e28f301fa6c00ffb3932a07b5c46e35b1 (patch)
tree2fda142dcb27c99fd8d0604479571a456ae9edfd
parentc0d520c9ab0646f96b73d17d2cc2ea5b933d6ab1 (diff)
Added EUI48Sub and EUI48::indexOf(const EUI48Sub& other) and contains(const EUI48Sub& other) (C++ and Java, both w/ unit tests)v2.2.0
-rw-r--r--api/direct_bt/BTAddress.hpp75
-rw-r--r--java/org/direct_bt/EUI48.java43
-rw-r--r--java/org/direct_bt/EUI48Sub.java106
-rw-r--r--test/direct_bt/test_btaddress01.cpp49
-rw-r--r--test/java/test/org/direct_bt/TestEUI48.java122
5 files changed, 391 insertions, 4 deletions
diff --git a/api/direct_bt/BTAddress.hpp b/api/direct_bt/BTAddress.hpp
index 01f54a37..eb1a4715 100644
--- a/api/direct_bt/BTAddress.hpp
+++ b/api/direct_bt/BTAddress.hpp
@@ -1,6 +1,6 @@
/*
* Author: Sven Gothel <[email protected]>
- * Copyright (c) 2020 Gothel Software e.K.
+ * Copyright (c) 2021 Gothel Software e.K.
* Copyright (c) 2020 ZAFENA AB
*
* Permission is hereby granted, free of charge, to any person obtaining
@@ -140,6 +140,44 @@ namespace direct_bt {
BDAddressType getBDAddressType(const HCILEOwnAddressType hciOwnAddrType) noexcept;
std::string getHCILEOwnAddressTypeString(const HCILEOwnAddressType type) noexcept;
+ struct EUI48Sub {
+ /**
+ * The <= 6 byte EUI48 sub-address.
+ */
+ uint8_t b[6]; // == sizeof(EUI48)
+
+ /**
+ * The actual length in bytes of the EUI48 sub-address, less or equal 6 bytes.
+ */
+ jau::nsize_t length;
+
+ constexpr EUI48Sub() noexcept : b{0}, length{0} { }
+ EUI48Sub(const uint8_t * b_, const jau::nsize_t len_) noexcept;
+
+ /**
+ * Construct a sub EUI48 via given string representation.
+ * <p>
+ * Implementation is consistent with EUI48Sub::toString().
+ * </p>
+ * @param str a string of less or equal of 17 characters representing less or equal of 6 bytes as hexadecimal numbers separated via colon,
+ * e.g. {@code "01:02:03:0A:0B:0C"}, {@code "01:02:03:0A"}, {@code ":"}, {@code ""}.
+ * @see EUI48Sub::toString()
+ */
+ EUI48Sub(const std::string mac);
+
+ constexpr EUI48Sub(const EUI48Sub &o) noexcept = default;
+ EUI48Sub(EUI48Sub &&o) noexcept = default;
+ constexpr EUI48Sub& operator=(const EUI48Sub &o) noexcept = default;
+ EUI48Sub& operator=(EUI48Sub &&o) noexcept = default;
+
+ /**
+ * Returns the EUI48 sub-string representation,
+ * less or equal 17 characters representing less or equal 6 bytes as upper case hexadecimal numbers separated via colon,
+ * e.g. {@code "01:02:03:0A:0B:0C"}, {@code "01:02:03:0A"}, {@code ""}.
+ */
+ std::string toString() const noexcept;
+ };
+
/**
* A packed 48 bit EUI-48 identifier, formerly known as MAC-48
* or simply network device MAC address (Media Access Control address).
@@ -156,11 +194,24 @@ namespace direct_bt {
/** EUI48 MAC address matching local device, i.e. '0:0:0:ff:ff:ff'. */
static const EUI48 LOCAL_DEVICE;
+ /**
+ * The 6 byte EUI48 address.
+ */
uint8_t b[6]; // == sizeof(EUI48)
constexpr EUI48() noexcept : b{0} { }
- EUI48(const uint8_t * b) noexcept;
+ EUI48(const uint8_t * b_) noexcept;
+
+ /**
+ * Construct instance via given string representation.
+ * <p>
+ * Implementation is consistent with EUI48::toString().
+ * </p>
+ * @param str a string of exactly 17 characters representing 6 bytes as hexadecimal numbers separated via colon {@code "01:02:03:0A:0B:0C"}.
+ * @see EUI48::toString()
+ */
EUI48(const std::string mac);
+
constexpr EUI48(const EUI48 &o) noexcept = default;
EUI48(EUI48 &&o) noexcept = default;
constexpr EUI48& operator=(const EUI48 &o) noexcept = default;
@@ -199,7 +250,25 @@ namespace direct_bt {
*/
BLERandomAddressType getBLERandomAddressType(const BDAddressType addressType) const noexcept;
- std::string toString() const;
+ /**
+ * Finds the index of given EUI48Sub.
+ */
+ jau::snsize_t indexOf(const EUI48Sub& other) const noexcept;
+
+ /**
+ * Returns true, if given EUI48Sub is contained in here.
+ * <p>
+ * If the sub is zero, true is returned.
+ * </p>
+ */
+ bool contains(const EUI48Sub& other) const noexcept;
+
+ /**
+ * Returns the EUI48 string representation,
+ * exactly 17 characters representing 6 bytes as upper case hexadecimal numbers separated via colon {@code "01:02:03:0A:0B:0C"}.
+ * @see EUI48::EUI48()
+ */
+ std::string toString() const noexcept;
} );
inline bool operator==(const EUI48& lhs, const EUI48& rhs) noexcept {
diff --git a/java/org/direct_bt/EUI48.java b/java/org/direct_bt/EUI48.java
index 2d4b2756..b17a1e63 100644
--- a/java/org/direct_bt/EUI48.java
+++ b/java/org/direct_bt/EUI48.java
@@ -57,7 +57,7 @@ public class EUI48 {
* Size of the byte stream representation in bytes
* @see #getStream(byte[], int)
*/
- private static final int byte_size = 6;
+ /* pp */ static final int byte_size = 6;
/**
* Construct instance via given string representation.
@@ -230,6 +230,47 @@ public class EUI48 {
}
/**
+ * Finds the index of given EUI48Sub.
+ */
+ public int indexOf(final EUI48Sub other) {
+ if( 0 == other.length ) {
+ return 0;
+ }
+ final byte first = other.b[0];
+ final int outerEnd = 6 - other.length + 1; // exclusive
+
+ for (int i = 0; i < outerEnd; i++) {
+ // find first char of other
+ while( b[i] != first ) {
+ if( ++i == outerEnd ) {
+ return -1;
+ }
+ }
+ if( i < outerEnd ) { // otherLen chars left to match?
+ // continue matching other chars
+ final int innerEnd = i + other.length; // exclusive
+ int j = i, k=0;
+ do {
+ if( ++j == innerEnd ) {
+ return i; // gotcha
+ }
+ } while( b[j] == other.b[++k] );
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns true, if given EUI48Sub is contained in here.
+ * <p>
+ * If the sub is zero, true is returned.
+ * </p>
+ */
+ public boolean contains(final EUI48Sub other) {
+ return 0 <= indexOf(other);
+ }
+
+ /**
* {@inheritDoc}
* <p>
* Returns the EUI48 string representation,
diff --git a/java/org/direct_bt/EUI48Sub.java b/java/org/direct_bt/EUI48Sub.java
new file mode 100644
index 00000000..a2cb6cf0
--- /dev/null
+++ b/java/org/direct_bt/EUI48Sub.java
@@ -0,0 +1,106 @@
+/**
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2021 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.direct_bt;
+
+/**
+ * A 48 bit EUI-48 sub-identifier, see {@link EUI48}.
+ */
+public class EUI48Sub {
+ /**
+ * The <= 6 byte EUI48 sub-address.
+ */
+ public final byte b[];
+
+ /**
+ * The actual length in bytes of the EUI48 sub-address, less or equal 6 bytes.
+ */
+ public final int length;
+
+ /**
+ * Construct a sub EUI48 via given string representation.
+ * <p>
+ * Implementation is consistent with {@link #toString()}.
+ * </p>
+ * @param str a string of less or equal of 17 characters representing less or equal of 6 bytes as hexadecimal numbers separated via colon,
+ * e.g. {@code "01:02:03:0A:0B:0C"}, {@code "01:02:03:0A"}, {@code ":"}, {@code ""}.
+ * @see #toString()
+ */
+ public EUI48Sub(final String str) throws IllegalArgumentException {
+ final int str_len = str.length();
+ if( 17 < str_len ) { // not exceeding EUI48.byte_size
+ throw new IllegalArgumentException("EUI48 sub-string must be less or equal length 17 but "+str.length()+": "+str);
+ }
+ final byte b_[] = new byte[ 6 ]; // intermediate result high -> low
+ int len_ = 0;
+ try {
+ int j=0;
+ while( j+1 < str_len /* && byte_count_ < byte_size */ ) { // min 2 chars left
+ if( ':' == str.charAt(j) ) {
+ ++j;
+ } else {
+ b_[len_] = Integer.valueOf(str.substring(j, j+2), 16).byteValue(); // b_: high->low
+ j += 2;
+ ++len_;
+ }
+ }
+ length = len_;
+ b = new byte[ length ];
+ for(j=0; j<length; ++j) { // swap low->high
+ b[j] = b_[length-1-j];
+ }
+ } catch (final NumberFormatException e) {
+ throw new IllegalArgumentException("EUI48 sub-string not in format '01:02:03:0A:0B:0C' but "+str, e);
+ }
+ }
+
+ /** Construct instance via given source byte array */
+ public EUI48Sub(final byte stream[], final int pos, final int len_) {
+ if( len_ > EUI48.byte_size || pos + len_ > stream.length ) {
+ throw new IllegalArgumentException("EUI48 stream ( pos "+pos+", len "+len_+" > EUI48 size "+EUI48.byte_size+" or stream.length "+stream.length);
+ }
+ b = new byte[len_];
+ System.arraycopy(stream, pos, b, 0, len_);
+ length = len_;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Returns the EUI48 sub-string representation,
+ * less or equal 17 characters representing less or equal 6 bytes as upper case hexadecimal numbers separated via colon,
+ * e.g. {@code "01:02:03:0A:0B:0C"}, {@code "01:02:03:0A"}, {@code ""}.
+ * </p>
+ */
+ @Override
+ public final String toString() {
+ final StringBuilder sb = new StringBuilder(17);
+ for(int i=length-1; 0 <= i; i--) {
+ BTUtils.byteHexString(sb, b[i], false /* lowerCase */);
+ if( 0 < i ) {
+ sb.append(":");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/test/direct_bt/test_btaddress01.cpp b/test/direct_bt/test_btaddress01.cpp
index 3c2896b0..1f4df2eb 100644
--- a/test/direct_bt/test_btaddress01.cpp
+++ b/test/direct_bt/test_btaddress01.cpp
@@ -9,11 +9,44 @@
#include <jau/test/catch2_ext.hpp>
#include <jau/basic_types.hpp>
+#include <jau/darray.hpp>
#include <direct_bt/BTAddress.hpp>
using namespace direct_bt;
using namespace jau;
+static void test_sub(const std::string& mac_str, const jau::darray<std::string>& mac_sub_strs, const jau::darray<jau::snsize_t>& indices) {
+ const EUI48 mac(mac_str);
+ printf("Test EUI48 mac: '%s' -> '%s'\n", mac_str.c_str(), mac.toString().c_str());
+ REQUIRE(mac_str == mac.toString());
+
+ int i=0;
+ jau::for_each_const(mac_sub_strs, [&i, &mac, &indices](const std::string &mac_sub_str) {
+ const EUI48Sub mac_sub(mac_sub_str);
+ printf("EUI48Sub mac02_sub: '%s' -> '%s'\n", mac_sub_str.c_str(), mac_sub.toString().c_str());
+ // cut-off pre- and post-colon in test string
+ std::string sub_str = mac_sub_str;
+ if( sub_str.size() > 0 && sub_str[0] == ':' ) {
+ sub_str = sub_str.substr(1, sub_str.size());
+ }
+ if( sub_str.size() > 0 && sub_str[sub_str.size()-1] == ':' ) {
+ sub_str = sub_str.substr(0, sub_str.size()-1);
+ }
+ REQUIRE(sub_str == mac_sub.toString());
+
+ jau::snsize_t idx = mac.indexOf(mac_sub);
+ REQUIRE( idx == indices.at(i));
+ if( idx >= 0 ) {
+ REQUIRE( mac.contains(mac_sub) );
+ } else {
+ REQUIRE( !mac.contains(mac_sub) );
+ }
+
+ ++i;
+ } );
+ (void) indices;
+}
+
TEST_CASE( "EUI48 Test 01", "[datatype][eui48]" ) {
EUI48 mac01;
INFO_STR("EUI48 size: whole0 "+std::to_string(sizeof(EUI48)));
@@ -21,4 +54,20 @@ TEST_CASE( "EUI48 Test 01", "[datatype][eui48]" ) {
INFO_STR("EUI48 size: data1 "+std::to_string(sizeof(mac01.b)));
REQUIRE_MSG("EUI48 struct and data size match", sizeof(EUI48) == sizeof(mac01));
REQUIRE_MSG("EUI48 struct and data size match", sizeof(mac01) == sizeof(mac01.b));
+
+ {
+ // index [high=5 ... low=0]
+ const std::string mac02_str = "C0:10:22:A0:10:00";
+ const jau::darray<std::string> mac02_sub_strs = { "C0", "C0:10", ":10:22", "10:22", ":10:22:", "10:22:", "10", "10:00", "00", ":", "", "00:10", mac02_str};
+ const jau::darray<jau::snsize_t> mac02_sub_idxs = { 5, 4, 3, 3, 3, 3, 1, 0, 0, 0, 0, -1, 0};
+ test_sub(mac02_str, mac02_sub_strs, mac02_sub_idxs);
+ }
+
+ {
+ // index [high=5 ... low=0]
+ const std::string mac03_str = "01:02:03:04:05:06";
+ const jau::darray<std::string> mac03_sub_strs = { "01", "01:02", ":03:04", "03:04", ":04:05:", "04:05:", "04", "05:06", "06", ":", "", "06:05", mac03_str};
+ const jau::darray<jau::snsize_t> mac03_sub_idxs = { 5, 4, 2, 2, 1, 1, 2, 0, 0, 0, 0, -1, 0};
+ test_sub(mac03_str, mac03_sub_strs, mac03_sub_idxs);
+ }
}
diff --git a/test/java/test/org/direct_bt/TestEUI48.java b/test/java/test/org/direct_bt/TestEUI48.java
new file mode 100644
index 00000000..e3551665
--- /dev/null
+++ b/test/java/test/org/direct_bt/TestEUI48.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright 2015 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+
+package test.org.direct_bt;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Test;
+import org.direct_bt.BTException;
+import org.direct_bt.BTFactory;
+import org.direct_bt.BTManager;
+import org.direct_bt.EUI48;
+import org.direct_bt.EUI48Sub;
+import org.jau.junit.util.JunitTracer;
+import org.junit.Assert;
+
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+/**
+ * Test basic EUI48 functionality
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TestEUI48 extends JunitTracer {
+
+ static {
+ final BTManager manager;
+ try {
+ manager = BTFactory.getDirectBTBluetoothManager();
+ } catch (BTException | NoSuchMethodException | SecurityException
+ | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | ClassNotFoundException e) {
+ System.err.println("Unable to instantiate DirectBT BluetoothManager");
+ e.printStackTrace();
+ System.exit(-1);
+ }
+ }
+ static void test_sub(final String mac_str, final List<String> mac_sub_strs, final List<Integer> indices) {
+ final EUI48 mac = new EUI48(mac_str);
+
+ System.out.printf("Test EUI48 mac: '%s' -> '%s'\n", mac_str, mac.toString());
+ Assert.assertEquals(mac_str, mac.toString());
+
+ int i=0;
+ for(final Iterator<String> iter=mac_sub_strs.iterator(); iter.hasNext(); ++i) {
+ final String mac_sub_str = iter.next();
+ final EUI48Sub mac_sub = new EUI48Sub(mac_sub_str);
+ System.out.printf("EUI48Sub mac02_sub: '%s' -> '%s'\n", mac_sub_str, mac_sub.toString());
+ // cut-off pre- and post-colon in test string
+ String sub_str = mac_sub_str;
+ if( sub_str.length() > 0 && sub_str.charAt(0) == ':' ) {
+ sub_str = sub_str.substring(1, sub_str.length());
+ }
+ if( sub_str.length() > 0 && sub_str.charAt(sub_str.length()-1) == ':' ) {
+ sub_str = sub_str.substring(0, sub_str.length()-1);
+ }
+ Assert.assertEquals(sub_str, mac_sub.toString());
+
+ final int idx = mac.indexOf(mac_sub);
+ Assert.assertEquals( idx, indices.get(i).intValue());
+ if( idx >= 0 ) {
+ Assert.assertTrue( mac.contains(mac_sub) );
+ } else {
+ Assert.assertFalse( mac.contains(mac_sub) );
+ }
+ }
+ }
+
+ @Test
+ public void test01_EUI48AndSub() {
+ {
+ // index [high=5 ... low=0]
+ final String mac02_str = "C0:10:22:A0:10:00";
+ final String[] mac02_sub_strs = { "C0", "C0:10", ":10:22", "10:22", ":10:22:", "10:22:", "10", "10:00", "00", ":", "", "00:10", mac02_str};
+ final Integer[] mac02_sub_idxs = { 5, 4, 3, 3, 3, 3, 1, 0, 0, 0, 0, -1, 0};
+ test_sub(mac02_str, Arrays.asList(mac02_sub_strs), Arrays.asList(mac02_sub_idxs));
+ }
+
+ {
+ // index [high=5 ... low=0]
+ final String mac03_str = "01:02:03:04:05:06";
+ final String[] mac03_sub_strs = { "01", "01:02", ":03:04", "03:04", ":04:05:", "04:05:", "04", "05:06", "06", ":", "", "06:05", mac03_str};
+ final Integer[] mac03_sub_idxs = { 5, 4, 2, 2, 1, 1, 2, 0, 0, 0, 0, -1, 0};
+ test_sub(mac03_str, Arrays.asList(mac03_sub_strs), Arrays.asList(mac03_sub_idxs));
+ }
+ }
+
+ public static void main(final String args[]) throws IOException {
+ final String tstname = TestEUI48.class.getName();
+ org.junit.runner.JUnitCore.main(tstname);
+ }
+
+}