diff options
author | Sven Gothel <[email protected]> | 2021-01-25 23:59:45 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2021-01-25 23:59:45 +0100 |
commit | ed76a94e28f301fa6c00ffb3932a07b5c46e35b1 (patch) | |
tree | 2fda142dcb27c99fd8d0604479571a456ae9edfd | |
parent | c0d520c9ab0646f96b73d17d2cc2ea5b933d6ab1 (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.hpp | 75 | ||||
-rw-r--r-- | java/org/direct_bt/EUI48.java | 43 | ||||
-rw-r--r-- | java/org/direct_bt/EUI48Sub.java | 106 | ||||
-rw-r--r-- | test/direct_bt/test_btaddress01.cpp | 49 | ||||
-rw-r--r-- | test/java/test/org/direct_bt/TestEUI48.java | 122 |
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); + } + +} |