All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tuwien.auto.calimero.DataUnitBuilder Maven / Gradle / Ivy

There is a newer version: 2.6-rc1
Show newest version
/*
    Calimero 2 - A library for KNX network access
    Copyright (c) 2006, 2015 B. Malinowsky

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Linking this library statically or dynamically with other modules is
    making a combined work based on this library. Thus, the terms and
    conditions of the GNU General Public License cover the whole
    combination.

    As a special exception, the copyright holders of this library give you
    permission to link this library with independent modules to produce an
    executable, regardless of the license terms of these independent
    modules, and to copy and distribute the resulting executable under terms
    of your choice, provided that you also meet, for each linked independent
    module, the terms and conditions of the license of that module. An
    independent module is a module which is not derived from or based on
    this library. If you modify this library, you may extend this exception
    to your version of the library, but you are not obligated to do so. If
    you do not wish to do so, delete this exception statement from your
    version.
*/

package tuwien.auto.calimero;

import tuwien.auto.calimero.exception.KNXIllegalArgumentException;
import tuwien.auto.calimero.log.LogManager;
import tuwien.auto.calimero.log.LogService;

/**
 * Creates, extracts and decodes information of protocol data units.
 * 

* The methods focus on transport layer and application layer data units. * * @author B. Malinowsky */ public final class DataUnitBuilder { /** Name of the log service used by data-unit builder methods. */ public static final String LOG_SERVICE = "Data-unit builder"; private static final LogService logger = LogManager.getManager().getLogService(LOG_SERVICE); private static final int T_DATA_CONNECTED = 0x40; private static final int T_CONNECT = 0x80; private static final int T_DISCONNECT = 0x81; private static final int T_ACK = 0xC2; private static final int T_NAK = 0xC3; private DataUnitBuilder() {} /** * Returns the application layer service of a given protocol data unit. *

* * @param apdu application layer protocol data unit, requires apdu.length * > 1 * @return APDU service code */ public static int getAPDUService(final byte[] apdu) { if (apdu.length < 2) throw new KNXIllegalArgumentException("getting APDU service from [0x" + toHex(apdu, "") + "], APCI length < 2"); // high 4 bits of APCI final int apci4 = (apdu[0] & 0x03) << 2 | (apdu[1] & 0xC0) >> 6; // lowest 6 bits of APCI final int apci6 = apdu[1] & 0x3f; // group value codes // group read if (apci4 == 0) { if (apci6 == 0) return 0; } // group response else if (apci4 == 1) return 0x40; // group write else if (apci4 == 2) return 0x80; // individual address codes else if (apci4 == 3 || apci4 == 4 || apci4 == 5) { if (apci6 == 0) return apci4 << 6; } // ADC codes else if (apci4 == 6 || apci4 == 7) return apci4 << 6; // memory codes else if (apci4 == 8 || apci4 == 9 || apci4 == 10) return apci4 << 6; // the rest else return apci4 << 6 | apci6; // unknown codes final int code = apci4 << 6 | apci6; logger.warn("unknown APCI service code 0x" + Integer.toHexString(code)); return code; } /** * Returns the transport layer service of a given protocol data unit. *

* * @param tpdu transport layer protocol data unit * @return TPDU service code */ public static int getTPDUService(final byte[] tpdu) { final int ctrl = tpdu[0] & 0xff; if ((ctrl & 0xFC) == 0) return 0; // 0x04 is tag_group service code, not used by us if ((ctrl & 0xFC) == 0x04) return 0x04; if ((ctrl & 0xC0) == 0x40) return T_DATA_CONNECTED; if (ctrl == T_CONNECT) return T_CONNECT; if (ctrl == T_DISCONNECT) return T_DISCONNECT; if ((ctrl & 0xC3) == T_ACK) return T_ACK; if ((ctrl & 0xC3) == T_NAK) return T_NAK; logger.warn("unknown TPCI service code 0x" + Integer.toHexString(ctrl)); return ctrl; } /** * Creates an application layer protocol data unit out of a service code and a service data * unit. *

* The transport layer bits in the first byte (TL / AL control field) are set 0. For creating a * length-optimized (compact) APDU, refer to {@link #createLengthOptimizedAPDU(int, byte[])}. * * @param service application layer service code * @param asdu application layer service data unit, asdu.length < 255 * @return APDU as byte array */ public static byte[] createAPDU(final int service, final byte[] asdu) { if (asdu.length > 254) throw new KNXIllegalArgumentException("ASDU length exceeds maximum of 254 bytes"); final byte[] apdu = new byte[2 + asdu.length]; apdu[0] = (byte) ((service >> 8) & 0x03); apdu[1] |= (byte) service; for (int i = 0; i < asdu.length; ++i) apdu[i + 2] = asdu[i]; return apdu; } /** * In the KNX standard, APDUs that use a shorter-length APDU format are referred to as * optimized, not compact; for naming reasons use * {@link #createLengthOptimizedAPDU(int, byte[])}. * * @param service application layer service code * @param asdu application layer service data unit, asdu.length < 255; or * null for no ASDU * @return APDU as byte array * @deprecated use {@link #createLengthOptimizedAPDU(int, byte[])} */ public static byte[] createCompactAPDU(final int service, final byte[] asdu) { final byte[] apdu = new byte[(asdu != null && asdu.length > 0) ? 1 + asdu.length : 2]; if (apdu.length > 255) throw new KNXIllegalArgumentException("APDU length exceeds maximum of 255 bytes"); apdu[0] = (byte) ((service >> 8) & 0x03); apdu[1] = (byte) service; if (asdu != null && asdu.length > 0) { // maximum of 6 bits in asdu[0] are valid apdu[1] |= asdu[0] & 0x3F; for (int i = 1; i < asdu.length; ++i) apdu[i + 1] = asdu[i]; } return apdu; } /** * Creates a length-optimized application layer protocol data unit out of a service * code and a service data unit. *

* The transport layer bits in the first byte (TL / AL control field) are set 0. If the compact * APDU shall not contain any ASDU information, asdu can be left null. * * @param service application layer service code * @param asdu application layer service data unit, asdu.length < 255; or * null for no ASDU * @return APDU as byte array */ public static byte[] createLengthOptimizedAPDU(final int service, final byte[] asdu) { return createCompactAPDU(service, asdu); } /** * Returns a copy of the ASDU contained in the supplied APDU. *

* The application layer service data unit (ASDU) is the APDU with the application * layer service code removed. * * @param apdu application layer protocol data unit for which to get the ASDU * @return the ASDU as byte array */ public static byte[] extractASDU(final byte[] apdu) { final int svc = getAPDUService(apdu); int offset = 2; int mask = 0xff; // 0x40 A_GroupValue_Response-PDU // 0x80 A_GroupValue_Write-PDU if (svc == 0x40 || svc == 0x80) { // only adjust for optimized read.res and write if (apdu.length <= 2) { offset = 1; mask = 0x3f; } } // 0x0180 A_ADC_Read-PDU // 0x01C0 A_ADC_Response-PDU else if (svc == 0x0180 || svc == 0x01C0) { offset = 1; mask = 0x3f; } // 0x0200 A_Memory_Read-PDU // 0x0240 A_Memory_Response-PDU // 0x0280 A_Memory_Write-PDU else if (svc == 0x0200 || svc == 0x0240 || svc == 0x0280) { offset = 1; // masks the number of bytes field, // the field size was increased to 6 bits in 03/03/07 2007 mask = 0x3f; } // 0x300 A_DeviceDescriptor_Read-PDU // 0x340 A_DeviceDescriptor_Response-PDU else if (svc == 0x300 || svc == 0x340) { offset = 1; // we mask the descriptor type mask = 0x3f; } // 0x100 A_IndividualAddress_Read-PDU // 0x140 A_IndividualAddress_Response-PDU else if (svc == 0x100 || svc == 0x140) { offset = 1; // we mask the lower 6 bits that need to be 0 mask = 0x3f; } // 0x380 A_Restart-PDU else if (svc == 0x380) { offset = 1; // we mask the response bit and restart type bit mask = 0x3f; } final byte[] asdu = new byte[apdu.length - offset]; for (int i = 0; i < asdu.length; ++i) asdu[i] = apdu[offset + i]; // some ASDUs have length 0, e.g., DoA.read if (asdu.length > 0) asdu[0] &= mask; return asdu; } /** * Decodes a protocol data unit into a textual representation. *

* Currently, the transport layer protocol control information (TPCI) and the * application layer protocol control information (APCI) is decoded. Decoding might be * extended in the future.
* The optional KNX destination address helps to determine the exact transport layer * service. * * @param tpdu transport layer protocol data unit to decode * @param dst KNX destination address belonging to the TPDU, might be * null * @return textual representation of control information in the TPDU */ public static String decode(final byte[] tpdu, final KNXAddress dst) { if (tpdu.length < 1) throw new KNXIllegalArgumentException("TPDU length too short"); final String s = decodeTPCI(tpdu[0] & 0xff, dst); if (tpdu.length > 1) return s + ", " + decodeAPCI(getAPDUService(tpdu)); return s; } /** * Decodes a transport layer protocol control information into a textual * representation. *

* * @param tpci transport layer protocol control information * @param dst KNX destination address belonging to the tpci, might be * null * @return textual representation of TPCI */ public static String decodeTPCI(final int tpci, final KNXAddress dst) { final int ctrl = tpci & 0xff; if ((ctrl & 0xFC) == 0) { if (dst == null) return "T-broadcast/group/ind"; if (dst.getRawAddress() == 0) return "T-broadcast"; if (dst instanceof GroupAddress) return "T-group"; return "T-individual"; } if ((ctrl & 0xC0) == 0x40) return "T-connected seq " + (ctrl >> 2 & 0xF); if (ctrl == T_CONNECT) return "T-connect"; if (ctrl == T_DISCONNECT) return "T-disconnect"; if ((ctrl & 0xC3) == T_ACK) return "T-ack seq " + (ctrl >> 2 & 0xF); if ((ctrl & 0xC3) == T_NAK) return "T-nak seq " + (ctrl >> 2 & 0xF); return "unknown TPCI"; } /** * Decodes an application layer protocol control information into a textual * representation. *

* * @param apci application layer protocol control information * @return textual representation of APCI */ public static String decodeAPCI(final int apci) { switch (apci) { case 0x00: return "A-group.read"; case 0x40: return "A-group.response"; case 0x80: return "A-group.write"; case 0x0180: return "A-ADC.read"; case 0x01C0: return "A-ADC.response"; case 0x03D1: return "A-authorize.read"; case 0x03D2: return "A-authorize.response"; case 0x3E0: return "A-domain.write"; case 0x3E1: return "A-domain.read"; case 0x3E2: return "A-domain.response"; case 0x3E3: return "A-domain-selective.read"; case 0x0100: return "A-ind.addr.read"; case 0x0140: return "A-ind.addr.response"; case 0xC0: return "A-ind.addr.write"; case 0x03DC: return "A-ind.addr-sno.read"; case 0x03DD: return "A-ind.addr_sno.response"; case 0x03DE: return "A-ind.addr_sno.write"; case 0x300: return "A-device-desc.read"; case 0x340: return "A-device-desc.response"; case 0x03D3: return "A-key.write"; case 0x03D4: return "A-key.response"; case 0x0200: return "A-memory.read"; case 0x0240: return "A-memory.response"; case 0x0280: return "A-memory.write"; case 0x03D8: return "A-property-desc.read"; case 0x03D9: return "A-property-desc.response"; case 0x03D5: return "A-property.read"; case 0x03D6: return "A-property.response"; case 0x03D7: return "A-property.write"; case 0x0380: return "A-restart"; default: return "unknown APCI"; } } /** * Returns the content of data as unsigned bytes in hexadecimal string * representation. *

* This method does not add hexadecimal prefixes (like 0x). * * @param data data array to format * @param sep separator to insert between 2 formatted data bytes, null * or "" for no gap between byte tokens * @return an unsigned hexadecimal string of data */ public static String toHex(final byte[] data, final String sep) { final StringBuffer sb = new StringBuffer(); for (int i = 0; i < data.length; ++i) { final int no = data[i] & 0xff; if (no < 0x10) sb.append('0'); sb.append(Integer.toHexString(no)); if (sep != null && i < data.length - 1) sb.append(sep); } return sb.toString(); } /** * Returns a new byte array of { data[from], data[from + 1], .. , data[min(to, * data.length) - 1] }. *

* * @param data source data array * @param from start of range index * @param to end of range index, exclusive * @return the byte array filled with the corresponding data elements */ public static byte[] copyOfRange(final byte[] data, final int from, final int to) { final byte[] range = new byte[Math.min(data.length, to) - from]; for (int i = 0; i < range.length; ++i) range[i] = data[from + i]; return range; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy