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

org.jetlinks.coap.options.BasicHeaderOptions Maven / Gradle / Ivy

There is a newer version: 1.0.0-RC2
Show newest version
package org.jetlinks.coap.options;

import org.jetlinks.coap.CoapPacket;
import org.jetlinks.coap.enums.Code;
import org.jetlinks.coap.exception.CoapMessageFormatException;
import org.jetlinks.coap.exception.CoapUnknownOptionException;
import org.jetlinks.coap.utils.DataConvertingUtility;
import org.jetlinks.coap.utils.HexArray;
import org.jetlinks.coap.utils.PacketUtils;
import org.jetlinks.coap.utils.StrictInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.text.ParseException;
import java.util.*;

/**
 * @author guyi
 */
@SuppressWarnings({"PMD.NPathComplexity", "PMD.CyclomaticComplexity"})
public class BasicHeaderOptions implements Serializable {

    public static final byte IF_MATCH = 1; //multiple
    public static final byte URI_HOST = 3;
    public static final byte ETAG = 4;      //multiple in request
    public static final byte IF_NON_MATCH = 5;
    public static final byte URI_PORT = 7;
    public static final byte LOCATION_PATH = 8; //multiple
    public static final byte URI_PATH = 11;  //multiple
    public static final byte CONTENT_FORMAT = 12;
    public static final byte MAX_AGE = 14;
    public static final byte URI_QUERY = 15; //multiple
    public static final byte ACCEPT = 17;   //multiple
    public static final byte LOCATION_QUERY = 20; //multiple
    public static final byte PROXY_URI = 35; //not repeatable
    public static final byte PROXY_SCHEME = 39; //not repeatable
    public static final byte SIZE1 = 60;
    //
    public static final short DEFAULT_MAX_AGE = 60;
    public static final String DEFAULT_URI_HOST = "";
    //
    private Short contentFormat;
    private Long maxAge;
    private byte[][] etag; //opaque
    private String uriHost;
    private String locationPath;
    private String locationQuery;
    private String uriPath;
    private String uriQuery;
    private Integer accept;
    private byte[][] ifMatch;
    private Boolean ifNonMatch;
    private String proxyUri;
    private String proxyScheme;
    private Integer uriPort;
    private Integer size1;
    private Map unrecognizedOptions;

    protected boolean parseOption(int type, byte[] data, Code code) {
        switch (type) {
            case CONTENT_FORMAT:
                setContentFormat(DataConvertingUtility.readVariableULong(data).shortValue());
                break;
            case MAX_AGE:
                setMaxAge(DataConvertingUtility.readVariableULong(data));
                break;
            case ETAG:
                etag = DataConvertingUtility.extendOption(etag, data);
                break;
            case URI_HOST:
                setUriHost(DataConvertingUtility.decodeToString(data));
                break;
            case LOCATION_PATH:
                locationPath = DataConvertingUtility.extendOption(locationPath, data, "/", true);
                break;
            case LOCATION_QUERY:
                locationQuery = DataConvertingUtility.extendOption(locationQuery, data, "&", false);
                break;
            case URI_PATH:
                uriPath = DataConvertingUtility.extendOption(uriPath, data, "/", true);
                break;
            case URI_QUERY:
                uriQuery = DataConvertingUtility.extendOption(uriQuery, data, "&", false);
                break;
            case PROXY_URI:
                proxyUri = DataConvertingUtility.decodeToString(data);
                break;
            case PROXY_SCHEME:
                proxyScheme = DataConvertingUtility.decodeToString(data);
                break;
            case ACCEPT:
                accept = DataConvertingUtility.readVariableULong(data).intValue();
                break;
            case IF_MATCH:
                ifMatch = DataConvertingUtility.extendOption(ifMatch, data);
                break;
            case IF_NON_MATCH:
                ifNonMatch = Boolean.TRUE;
                break;
            case URI_PORT:
                uriPort = DataConvertingUtility.readVariableULong(data).intValue();
                break;
            case SIZE1:
                size1 = DataConvertingUtility.readVariableULong(data).intValue();
                break;
            default:
                return false;
        }
        return true;
    }

    /**
     * Returns value for given un-recognize option number.
     *
     * @param optNumber option number
     * @return byte array value or null if does not exist
     */
    public byte[] getCustomOption(Integer optNumber) {
        if (!unrecognizedOptions.containsKey(optNumber)) {
            return null;
        }
        return unrecognizedOptions.get(optNumber).getFirstValue();
    }

    public void criticalOptTest() throws CoapUnknownOptionException {
        if (unrecognizedOptions == null) {
            return;
        }
        for (int tp : unrecognizedOptions.keySet()) {
            if (isCritical(tp)) {
                throw new CoapUnknownOptionException(tp);
            }
        }
    }

    public static boolean isCritical(int optionNumber) {
        return (optionNumber & 1) != 0;
    }

    public static boolean isUnsave(int optionNumber) {
        return (optionNumber & 2) != 0;
    }

    public static boolean hasNoCacheKey(int optionNumber) {
        return (optionNumber & 0x1e) == 0x1c;
    }

    /**
     * Adds header option
     *
     * @param optionNumber option number
     * @param data option value as byte array
     * @return true if header type is a known, false for unknown header option
     */
    public final boolean put(int optionNumber, byte[] data, Code code) {
        if (parseOption(optionNumber, data, code)) {
            return true;
        }
        //unrecognizeg option header
        if (unrecognizedOptions == null) {
            unrecognizedOptions = new HashMap<>();
        }
        unrecognizedOptions.put(optionNumber, new RawOption(optionNumber, data));
        return true;
    }

    public final boolean put(int optionNumber, byte[] data) {
        return put(optionNumber, data, null);
    }

    /**
     * Returns list with header options.
     *
     * @return sorted list
     */
    protected List getRawOptions() {
        LinkedList list = new LinkedList<>();

        if (contentFormat != null) {
            list.add(RawOption.fromUint(CONTENT_FORMAT, contentFormat.longValue()));
        }
        if (maxAge != null && maxAge != DEFAULT_MAX_AGE) {
            list.add(RawOption.fromUint(MAX_AGE, maxAge));
        }
        if (etag != null) {
            list.add(new RawOption(ETAG, etag));
        }
        if (uriHost != null && !uriHost.equals(DEFAULT_URI_HOST)) {
            list.add(RawOption.fromString(URI_HOST, uriHost));
        }
        if (locationPath != null) {
            list.add(RawOption.fromString(LOCATION_PATH, DataConvertingUtility.split(locationPath, '/')));
        }
        if (locationQuery != null) {
            list.add(RawOption.fromString(LOCATION_QUERY, locationQuery.split("&")));
        }
        if (uriPath != null && !uriPath.equals("/")) {
            list.add(RawOption.fromString(URI_PATH, DataConvertingUtility.split(uriPath, '/')));
        }
        if (uriQuery != null) {
            list.add(RawOption.fromString(URI_QUERY, uriQuery.split("&")));
        }
        if (proxyUri != null) {
            list.add(RawOption.fromString(PROXY_URI, proxyUri));
        }
        if (proxyScheme != null) {
            list.add(RawOption.fromString(PROXY_SCHEME, proxyScheme));
        }
        if (this.accept != null) {
            list.add(RawOption.fromUint(ACCEPT, accept.longValue()));
        }
        if (this.uriPort != null) {
            list.add(RawOption.fromUint(URI_PORT, uriPort.longValue()));
        }
        if (ifNonMatch != null && ifNonMatch) {
            list.add(RawOption.fromEmpty(IF_NON_MATCH));
        }
        if (ifMatch != null) {
            list.add(new RawOption(IF_MATCH, ifMatch));
        }
        if (size1 != null) {
            list.add(RawOption.fromUint(SIZE1, size1.longValue()));
        }

        if (unrecognizedOptions != null) {
            for (RawOption rOpt : unrecognizedOptions.values()) {
                list.add(rOpt);
            }
        }
        return list;
    }

    /**
     * Returns number of header options. If greater that 14 then returns 15.
     *
     * @return option count
     */
    final byte getOptionCount() {
        int optCount = 0;
        for (RawOption rOpt : getRawOptions()) {
            optCount += rOpt.optValues.length == 0 ? 1 : rOpt.optValues.length;
        }
        return (byte) (optCount > 14 ? 15 : optCount);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        toString(sb, null);
        return sb.toString();
    }

    public void toString(StringBuilder sb, Code code) {
        if (uriPath != null) {
            sb.append(" URI:").append(uriPath);
        }
        if (uriQuery != null) {
            sb.append('?').append(uriQuery);
        }
        if (locationPath != null) {
            sb.append(" Loc:").append(locationPath);
            if (locationQuery != null) {
                sb.append('?').append(locationQuery);
            }
        } else if (locationQuery != null) {
            sb.append(" Loc:?").append(locationQuery);
        }
        if (etag != null && etag.length > 0) {
            sb.append(" ETag:").append(HexArray.toHex(etag[0]));
        }
        if (maxAge != null) {
            sb.append(" MaxAge:").append(maxAge).append('s');
        }
        if (contentFormat != null) {
            sb.append(" ContTp:").append(contentFormat);
        }
        if (uriHost != null) {
            sb.append(" Host:").append(uriHost);
        }
        if (unrecognizedOptions != null) {
            for (RawOption rOpt : unrecognizedOptions.values()) {
                if (rOpt.optValues.length > 0) {
                    sb.append(" H").append(rOpt.optNumber).append(":0x").append(HexArray.toHexShort(rOpt.optValues[0], 10));
                }
            }
        }
        if (proxyUri != null) {
            sb.append(" Proxy:");
            if (proxyScheme != null) {
                sb.append(proxyScheme).append("::/");
            }
            sb.append(proxyUri);
        }
        if (ifMatch != null && ifMatch.length > 0) {
            sb.append(" ifMatch:").append(HexArray.toHex(ifMatch[0]));
        }
        if (ifNonMatch != null && ifNonMatch) {
            sb.append(" ifNonMatch");
        }
        if (accept != null) {
            sb.append(" accept:").append(accept);
        }
        if (size1 != null) {
            sb.append(" sz1:").append(size1);
        }
    }

    /**
     * Returns content format.
     *
     * @return content format
     */
    public Short getContentFormat() {
        return contentFormat;
    }

    /**
     * Sets content format
     *
     * @param contentFormat content format
     */
    public void setContentFormat(Short contentFormat) {
        this.contentFormat = contentFormat;
    }

    /**
     * The Max-age Option indicates the maximum age of the resource for use in
     * cache control in seconds. The option value is represented as a variable
     * length unsigned integer between 8 and 32 bits. A default value of 60
     * seconds is assumed in the absence of this option.
     * 

* When included in a request, Max-age indicates the maximum age of a cached * representation of that resource the client will accept. When included in * a response, Max-age indicates the maximum time the representation may be * cached before it MUST be discarded. This option MUST NOT occur more than * once in a header. *

* * @return max-age in seconds or null if absent */ public final Long getMaxAge() { return maxAge; } /** * @return max-age in seconds */ public final long getMaxAgeValue() { return maxAge != null ? maxAge : DEFAULT_MAX_AGE; } /** * The option value is an integer number of seconds between 0 and 2^32-1 * inclusive (about 136.1 years). A default value of 60 seconds is assumed * in the absence of the option in a response. * * @param maxAge max-age to set in seconds */ public final void setMaxAge(Long maxAge) { this.maxAge = maxAge == null ? null : maxAge & 0xFFFFFFFFL; } /** * @return first etag from array or null of array is empty */ public final byte[] getEtag() { return etag == null ? null : etag[0]; } /** * @param etag the etag to set */ public final void setEtag(byte[] etag) { if (etag == null || etag.length == 0) { this.etag = null; return; } if (etag.length > 8) { throw new IllegalArgumentException("Wrong ETAG option value, should be in range 1-8"); } this.etag = new byte[][]{etag}; } public byte[][] getEtagArray() { return etag; } public void setEtag(byte[][] etag) { //test etag for (int i = 0; i < etag.length; i++) { if (etag[i].length == 0 || etag[i].length > 8) { throw new IllegalArgumentException("Wrong ETAG option value, should be in range 1-8"); } } this.etag = etag; } /** * @return the uriAuthority */ public final String getUriAuthority() { return uriHost; } /** * @param uriHost the uriHost to set */ public final void setUriHost(String uriHost) { this.uriHost = uriHost; } public String getUriHost() { return uriHost; } /** * @return the location */ public final String getLocationPath() { return locationPath; } /** * @param location the location to set */ public final void setLocationPath(String location) { if (location != null && (location.equals(".") || location.equals(".."))) { throw new IllegalArgumentException("Illegal Location-Path: " + location); } if (location == null || location.isEmpty()) { this.locationPath = null; } else { this.locationPath = location; } } /** * @return the uriPath */ public final String getUriPath() { return uriPath; } /** * Sets uri-path. If not null or empty it must start with '/' character. * * @param uriPath the uriPath to set */ public final void setUriPath(String uriPath) { if (uriPath != null && (uriPath.length() == 0 || uriPath.equals("/"))) { this.uriPath = null; } else { if (uriPath != null && uriPath.charAt(0) != '/') { throw new IllegalArgumentException("Uri-path must start with '/' character."); } this.uriPath = uriPath; } } /** * @return the uriQuery */ public String getUriQuery() { return uriQuery; } /** * @param uriQuery the uriQuery to set */ public void setUriQuery(String uriQuery) { if (uriQuery.isEmpty()) { this.uriQuery = null; } else { this.uriQuery = uriQuery; } } public void setAccept(short accept) { setAccept(((int) accept)); } public void setAccept(Integer accept) { if (accept != null && (accept < 0 || accept > 0xFFFF)) { throw new IllegalArgumentException(); } this.accept = accept; } public Integer getAccept() { return accept; } public byte[][] getIfMatch() { return ifMatch; } public void setIfMatch(byte[][] ifMatch) { this.ifMatch = ifMatch; } public Boolean getIfNonMatch() { return ifNonMatch; } public void setIfNonMatch(Boolean ifNonMatch) { this.ifNonMatch = ifNonMatch; } public String getLocationQuery() { return locationQuery; } public void setLocationQuery(String locationQuery) { this.locationQuery = locationQuery; } public void setProxyUri(String proxyUri) { this.proxyUri = proxyUri; } public String getProxyUri() { return proxyUri; } public void setProxyScheme(String proxyScheme) { this.proxyScheme = proxyScheme; } public String getProxyScheme() { return proxyScheme; } public Integer getUriPort() { return uriPort; } public void setUriPort(Integer uriPort) { this.uriPort = uriPort; } public Map getUriQueryMap() throws ParseException { if (uriQuery == null) { return Collections.emptyMap(); } return DataConvertingUtility.parseUriQuery(uriQuery); } public Integer getSize1() { return size1; } public void setSize1(Integer size) { this.size1 = size; } public void serialize(OutputStream os) throws IOException { List list = getRawOptions(); Collections.sort(list); int lastOptNumber = 0; for (RawOption opt : list) { for (byte[] optValue : opt.optValues) { int delta = opt.optNumber - lastOptNumber; lastOptNumber = opt.optNumber; if (delta > 0xFFFF + 269) { throw new IllegalArgumentException("Delta with size: " + delta + " is not supported [option number: " + opt.optNumber + "]"); } int len = optValue.length; if (len > 0xFFFF + 269) { throw new IllegalArgumentException("Header size: " + len + " is not supported [option number: " + opt.optNumber + "]"); } writeOptionHeader(delta, len, os); os.write(optValue); } } } static void writeOptionHeader(int delta, int len, OutputStream os) throws IOException { //first byte int tempByte; if (delta <= 12) { tempByte = delta << 4; } else if (delta < 269) { tempByte = 13 << 4; } else { tempByte = 14 << 4; } if (len <= 12) { tempByte |= len; } else if (len < 269) { tempByte |= 13; } else { tempByte |= 14; } os.write(tempByte); //extended option delta if (delta > 12 && delta < 269) { os.write(delta - 13); } else if (delta >= 269) { os.write((0xFF00 & (delta - 269)) >> 8); os.write(0x00FF & (delta - 269)); } //extended len if (len > 12 && len < 269) { os.write(len - 13); } else if (len >= 269) { os.write((0xFF00 & (len - 269)) >> 8); os.write(0x00FF & (len - 269)); } } public boolean deserialize(InputStream inputStream, Code code) throws IOException, CoapMessageFormatException { try { return deserialize(inputStream, code, true, Optional.empty()) != 0; } catch (PacketUtils.NotEnoughDataException e) { // should never happen throw new IOException(e); } } int deserialize(InputStream inputStream, Code code, boolean orBlock, Optional optionsAndPayloadLen) throws IOException, CoapMessageFormatException, PacketUtils.NotEnoughDataException { StrictInputStream is = null; if (inputStream instanceof StrictInputStream) { is = (StrictInputStream) inputStream; } else { is = new StrictInputStream(inputStream); } int streamAvailable = is.available(); int availableInternal = optionsAndPayloadLen.orElse(streamAvailable); int headerOptNum = 0; while (availableInternal > 0) { int hdrByte = PacketUtils.read8(is, orBlock); availableInternal--; if (hdrByte == CoapPacket.PAYLOAD_MARKER) { return availableInternal; } int delta = hdrByte >> 4; int len = 0xF & hdrByte; if (delta == 15 || len == 15) { throw new CoapMessageFormatException("Unexpected delta or len value in option header after optNum: " + headerOptNum); } if (delta == 13) { delta += PacketUtils.read8(is, orBlock); availableInternal--; } else if (delta == 14) { delta = PacketUtils.read16(is, orBlock) + 269; availableInternal -= 2; } if (len == 13) { len += PacketUtils.read8(is, orBlock); availableInternal--; } else if (len == 14) { len = PacketUtils.read16(is, orBlock) + 269; availableInternal -= 2; } headerOptNum += delta; byte[] headerOptData = PacketUtils.readN(is, len, orBlock); availableInternal -= len; put(headerOptNum, headerOptData, code); } if (availableInternal < 0) { throw new CoapMessageFormatException("No payload marker found and options read more that were available"); } return availableInternal; } @Override public int hashCode() { int hash = 7; hash = 41 * hash + (this.contentFormat != null ? this.contentFormat.hashCode() : 0); hash = 41 * hash + (this.maxAge != null ? this.maxAge.hashCode() : 0); hash = 41 * hash + Arrays.deepHashCode(this.etag); hash = 41 * hash + (this.uriHost != null ? this.uriHost.hashCode() : 0); hash = 41 * hash + (this.locationPath != null ? this.locationPath.hashCode() : 0); hash = 41 * hash + (this.locationQuery != null ? this.locationQuery.hashCode() : 0); hash = 41 * hash + (this.uriPath != null ? this.uriPath.hashCode() : 0); hash = 41 * hash + (this.uriQuery != null ? this.uriQuery.hashCode() : 0); hash = 41 * hash + (this.accept != null ? this.accept.hashCode() : 0); hash = 41 * hash + Arrays.deepHashCode(this.ifMatch); hash = 41 * hash + (this.ifNonMatch != null ? this.ifNonMatch.hashCode() : 0); hash = 41 * hash + (this.proxyUri != null ? this.proxyUri.hashCode() : 0); hash = 41 * hash + (this.proxyScheme != null ? this.proxyScheme.hashCode() : 0); hash = 41 * hash + (this.uriPort != null ? this.uriPort.hashCode() : 0); hash = 41 * hash + (this.size1 != null ? this.size1.hashCode() : 0); hash = 41 * hash + (this.unrecognizedOptions != null ? this.unrecognizedOptions.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final BasicHeaderOptions other = (BasicHeaderOptions) obj; if (this.contentFormat != other.contentFormat && (this.contentFormat == null || !this.contentFormat.equals(other.contentFormat))) { return false; } if (this.maxAge != other.maxAge && (this.maxAge == null || !this.maxAge.equals(other.maxAge))) { return false; } if (!Arrays.deepEquals(this.etag, other.etag)) { return false; } if ((this.uriHost == null) ? (other.uriHost != null) : !this.uriHost.equals(other.uriHost)) { return false; } if ((this.locationPath == null) ? (other.locationPath != null) : !this.locationPath.equals(other.locationPath)) { return false; } if ((this.locationQuery == null) ? (other.locationQuery != null) : !this.locationQuery.equals(other.locationQuery)) { return false; } if ((this.uriPath == null) ? (other.uriPath != null) : !this.uriPath.equals(other.uriPath)) { return false; } if ((this.uriQuery == null) ? (other.uriQuery != null) : !this.uriQuery.equals(other.uriQuery)) { return false; } if ((this.accept == null) ? (other.accept != null) : !this.accept.equals(other.accept)) { return false; } if (!Arrays.deepEquals(this.ifMatch, other.ifMatch)) { return false; } if (this.ifNonMatch != other.ifNonMatch && (this.ifNonMatch == null || !this.ifNonMatch.equals(other.ifNonMatch))) { return false; } if ((this.proxyUri == null) ? (other.proxyUri != null) : !this.proxyUri.equals(other.proxyUri)) { return false; } if ((this.uriPort == null) ? (other.uriPort != null) : !this.uriPort.equals(other.uriPort)) { return false; } if ((this.size1 == null) ? (other.size1 != null) : !this.size1.equals(other.size1)) { return false; } if ((this.proxyScheme == null) ? (other.proxyScheme != null) : !this.proxyScheme.equals(other.proxyScheme)) { return false; } if (this.unrecognizedOptions != other.unrecognizedOptions && (this.unrecognizedOptions == null || !this.unrecognizedOptions.equals(other.unrecognizedOptions))) { return false; } return true; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy