OscoreOptionEncoder.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.io.ByteArrayOutputStream;
import java.io.IOException;

import org.eclipse.californium.elements.util.Bytes;

/**
 * Class for encoding 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 OscoreOptionEncoder {

	private boolean encoded;
	private byte[] encodedBytes;

	private byte[] idContext;
	private byte[] partialIV;
	private byte[] kid;
	boolean groupFlag;

	/**
	 * Retrieve the encoded bytes of the OSCORE option.
	 * 
	 * @return the encoded OSCORE option bytes
	 */
	public byte[] getBytes() {
		if (!encoded) {
			encodedBytes = encode();
		}

		return encodedBytes;
	}

	/**
	 * Encode the set parameters into the bytes of the OSCORE option.
	 * 
	 * @return the bytes of the OSCORE option
	 */
	private byte[] encode() {
		int firstByte = 0x00;
		ByteArrayOutputStream bRes = new ByteArrayOutputStream();

		boolean hasContextID = this.idContext != null;
		boolean hasPartialIV = this.partialIV != null;
		boolean hasKid = this.kid != null;

		// If the Context ID should be included, set its bit
		if (hasContextID) {
			firstByte = firstByte | 0x10;
		}

		// If the KID should be included, set its bit
		if (hasKid) {
			firstByte = firstByte | 0x08; // Set the KID bit
		}

		// If the Group Flag is set, set its bit
		if (groupFlag) {
			firstByte = firstByte | 0x20;
		}

		// If the Partial IV should be included, encode it
		if (hasPartialIV) {
			byte[] partialIV = this.partialIV;
			firstByte = firstByte | (partialIV.length & 0x07);

			bRes.write(firstByte);
			try {
				bRes.write(partialIV);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			bRes.write(firstByte);
		}

		// Encode the Context ID length and value if to be included
		if (hasContextID) {
			try {
				bRes.write(this.idContext.length);
				bRes.write(this.idContext);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		// Encode Sender ID (KID)
		if (hasKid) {
			try {
				bRes.write(this.kid);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		// Set the option as encoded
		encoded = true;

		// If the OSCORE option is length 1 and 0x00, it should be empty
		// https://tools.ietf.org/html/draft-ietf-core-object-security-16#section-2
		byte[] optionBytes = bRes.toByteArray();
		if (optionBytes.length == 1 && optionBytes[0] == 0x00) {
			return Bytes.EMPTY;
		} else {
			return optionBytes;
		}
	}

	/**
	 * Retrieve the set ID Context
	 * 
	 * @return the ID Context (kid context)
	 */
	public byte[] getIdContext() {
		return idContext;
	}

	/**
	 * Set the ID Context
	 * 
	 * @param idContext the ID Context (kid context) to set
	 */
	public void setIdContext(byte[] idContext) {
		encoded = false;
		this.idContext = idContext;
	}

	/**
	 * Retrieve the set Partial IV
	 * 
	 * @return the Partial IV
	 */
	public byte[] getPartialIV() {
		return partialIV;
	}

	/**
	 * Set the Partial IV
	 * 
	 * @param partialIV the Partial IV to set
	 */
	public void setPartialIV(byte[] partialIV) {
		encoded = false;
		this.partialIV = partialIV;
	}

	/**
	 * Set the Partial IV (based on an integer sequence number)
	 * 
	 * @param senderSeq the sequence number to set as Partial IV
	 */
	public void setPartialIV(int senderSeq) {
		encoded = false;
		this.partialIV = OSSerializer.processPartialIV(senderSeq);
	}

	/**
	 * Retrieve the set KID
	 * 
	 * @return the KID
	 */
	public byte[] getKid() {
		return kid;
	}

	/**
	 * Set the KID
	 * 
	 * @param kid the KID to set
	 */
	public void setKid(byte[] kid) {
		encoded = false;
		this.kid = kid;
	}

	/**
	 * Retrieve the value of the Group Mode bit
	 * 
	 * @return the KID
	 */
	public boolean getGroupFlag() {
		return groupFlag;
	}

	/**
	 * Set the Group Mode bit in the flag bytes
	 * 
	 * @param b the value of the bit to set
	 */
	public void setGroupFlag(boolean b) {
		encoded = false;
		this.groupFlag = b;

	}

}