OscoreOptionDecoder.java
/*******************************************************************************
* Copyright (c) 2025 RISE and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Rikard Höglund (RISE)
*
******************************************************************************/
package org.eclipse.californium.oscore;
import java.util.Arrays;
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
import org.eclipse.californium.elements.util.DatagramReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for decoding the bytes of an OSCORE CoAP option.
*
* See the structure of the option:
* https://datatracker.ietf.org/doc/html/rfc8613#section-6.1
*
*/
public class OscoreOptionDecoder {
/**
* The logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OscoreOptionDecoder.class);
private byte[] encodedBytes;
private byte[] idContext;
private byte[] partialIV;
private byte[] kid;
private int n;
private int k;
private int h;
/**
* Initialize the OSCORE option with a certain array of bytes and decode
* them into the parameters of the option.
*
* @param encodedBytes the encoded bytes of the option
* @throws CoapOSException if the option is malformed
*/
public OscoreOptionDecoder(byte[] encodedBytes) throws CoapOSException {
this.encodedBytes = encodedBytes;
decode();
}
/**
* Set the OSCORE option to a certain array of bytes and decode them into
* the parameters of the option.
*
* @param encodedBytes the encoded bytes of the option
* @throws CoapOSException if the option is malformed
*/
public void setBytes(byte[] encodedBytes) throws CoapOSException {
this.encodedBytes = encodedBytes;
decode();
}
/**
* Performs the decoding of the option and stores the resulting parameters
* in this object.
*
* @throws CoapOSException if the option is malformed
*/
private void decode() throws CoapOSException {
byte[] total = encodedBytes;
/**
* If the OSCORE option value is a zero length byte array it represents
* a byte array of length 1 with a byte 0x00 See
* https://tools.ietf.org/html/draft-ietf-core-object-security-16#section-2
*/
if (total.length == 0) {
total = new byte[] { 0x00 };
}
byte flagByte = total[0];
int n = flagByte & 0x07;
int k = (flagByte & 0x08) >> 3;
int h = (flagByte & 0x10) >> 4;
byte[] partialIV = null;
byte[] kid = null;
byte[] kidContext = null;
int index = 1;
try {
// Parsing Partial IV
if (n > 0) {
partialIV = Arrays.copyOfRange(total, index, index + n);
index += n;
}
} catch (Exception e) {
LOGGER.error("Failed to parse Partial IV in OSCORE option.");
throw new CoapOSException(ErrorDescriptions.FAILED_TO_DECODE_COSE, ResponseCode.BAD_OPTION);
}
try {
// Parsing KID Context
if (h != 0) {
int s = total[index++];
kidContext = Arrays.copyOfRange(total, index, index + s);
index += s;
}
} catch (Exception e) {
LOGGER.error("Failed to parse KID Context in OSCORE option.");
throw new CoapOSException(ErrorDescriptions.FAILED_TO_DECODE_COSE, ResponseCode.BAD_OPTION);
}
try {
// Parsing KID
if (k != 0) {
kid = Arrays.copyOfRange(total, index, total.length);
}
} catch (Exception e) {
LOGGER.error("Failed to parse KID in OSCORE option.");
throw new CoapOSException(ErrorDescriptions.FAILED_TO_DECODE_COSE, ResponseCode.BAD_OPTION);
}
// Check option length consistency
if (k == 0 && index != total.length) {
// If KID is not present there should be no further data
LOGGER.error("Extranous data at end of OSCORE option.");
throw new CoapOSException(ErrorDescriptions.FAILED_TO_DECODE_COSE, ResponseCode.BAD_OPTION);
}
// Store parsed data in this object
this.n = n;
this.k = k;
this.h = h;
this.partialIV = partialIV;
this.kid = kid;
this.idContext = kidContext;
}
/**
* Retrieve the ID Context
*
* @return the ID Context (kid context)
*/
public byte[] getIdContext() {
return idContext;
}
/**
* Retrieve the Partial IV
*
* @return the Partial IV
*/
public byte[] getPartialIV() {
return partialIV;
}
/**
* Retrieve the sequence number
*
* @return the sequence number (based on the Partial IV)
*/
public int getSequenceNumber() {
if (partialIV == null) {
return 0;
}
return new DatagramReader(partialIV, false).read(partialIV.length * Byte.SIZE);
}
/**
* Retrieve the KID
*
* @return the KID
*/
public byte[] getKid() {
return kid;
}
/**
* Retrieve the n flag bit
*
* @return the n bit (length of Partial IV)
*/
public int getN() {
return n;
}
/**
* Retrieve the k flag bit
*
* @return the k bit (if KID is present)
*/
public int getK() {
return k;
}
/**
* Retrieve the h flag bit
*
* @return the h bit (if ID Context is present)
*/
public int getH() {
return h;
}
}