/*
* Copyright Ericsson AB 2011-2014. All Rights Reserved.
*
* The contents of this file are subject to the Lesser GNU Public License,
* (the "License"), either version 2.1 of the License, or
* (at your option) any later version.; you may not use this file except in
* compliance with the License. You should have received a copy of the
* License along with this software. If not, it can be
* retrieved online at https://www.gnu.org/licenses/lgpl.html. Moreover
* it could also be requested from Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
* WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
* EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
* OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND,
* EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
* LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE,
* YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
*
* IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
* WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
* REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
* DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
* DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY
* (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
* INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE
* OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
* HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
*/
package com.ericsson.deviceaccess.coap.basedriver.util;
import com.ericsson.common.util.BitUtil;
import static com.ericsson.common.util.BitUtil.getBitsInIntAsByte;
import static com.ericsson.common.util.BitUtil.getBitsInIntAsInt;
import static com.ericsson.common.util.BitUtil.setBitsInByte;
import static com.ericsson.common.util.BitUtil.splitIntToBytes;
import com.ericsson.deviceaccess.coap.basedriver.api.message.CoAPCode;
import com.ericsson.deviceaccess.coap.basedriver.api.message.CoAPMessage;
import com.ericsson.deviceaccess.coap.basedriver.api.message.CoAPMessageFormat;
import com.ericsson.deviceaccess.coap.basedriver.api.message.CoAPOptionHeader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is responsible for encoding a CoAPMessage (either request or
* response) into a byte array
*/
public class CoAPMessageWriter implements CoAPMessageFormat {
private static final Logger LOGGER = LoggerFactory.getLogger(CoAPMessageWriter.class);
private final CoAPMessage message;
private final ByteArrayOutputStream outputStream;
/**
* Constructor.
*
* @param message message to be encoded
*/
public CoAPMessageWriter(CoAPMessage message) {
this.message = message;
this.outputStream = new ByteArrayOutputStream();
}
/**
* This method actually does the encoding.
*
* @return encoded byte array
* @throws
* com.ericsson.deviceaccess.coap.basedriver.api.message.CoAPMessageFormat.IncorrectMessageException
*/
public byte[] encode() throws IncorrectMessageException {
LOGGER.debug("CoAPMessageWriter: encode a message with message ID " + message.getIdentifier());
// These are the header that all messages should have
int version = message.getVersion();
int messageType = message.getMessageType().getNo();
byte[] token = message.getToken();
int tokenLength = token == null ? 0 : token.length;
if (tokenLength > 8) {
//MUST NOT be send and MUST be processed as a message format error
throw new IncorrectMessageException("Wrong token length: " + tokenLength); //TODO: Handle this
}
CoAPCode messageCode = message.getCode();
// First byte with version, message type & option count
byte headerByte = setBitsInByte(0, VERSION_START,
VERSION_LENGTH,
getBitsInIntAsByte(version, 0, VERSION_LENGTH));
headerByte = setBitsInByte(headerByte, TYPE_START,
TYPE_LENGTH,
getBitsInIntAsByte(messageType, 0, TYPE_LENGTH));
headerByte = setBitsInByte(headerByte,
TOKEN_LENGTH_START, TOKEN_LENGTH_LENGTH,
getBitsInIntAsByte(tokenLength, 0, TOKEN_LENGTH_LENGTH));
outputStream.write(headerByte);
byte codeByte = setBitsInByte(0, CODE_START,
CODE_LENGTH,
getBitsInIntAsByte(messageCode.getNo(), 0, CODE_LENGTH));
outputStream.write(codeByte);
// Message ID is a 16-bit unsigned => two bytes
byte[] messageIdBytes = splitIntToBytes(message.getMessageId());
outputStream.write(messageIdBytes[2]);
outputStream.write(messageIdBytes[3]);
for (int n = 0; n < tokenLength; n++) {
outputStream.write(token[n]);
}
this.encodeOptionHeaders();
if (message.getPayload() != null && message.getPayload().length > 0) {
try {
outputStream.write(PAYLOAD_MARKER);
outputStream.write(message.getPayload());
} catch (IOException e) {
LOGGER.debug("Writing payload failed.", e);
}
}
byte[] byteArray = outputStream.toByteArray();
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
LOGGER.debug("Ending message writing failed.", e);
}
return byteArray;
}
/**
* This method encodes the option headers that are part of the message
*/
private void encodeOptionHeaders() {
// Sort options
// TODO is there better to handle this??
List<CoAPOptionHeader> options = message.getOptionHeaders();
Collections.sort(options);
// MultiMap options = message.getOptionHeaders();
int previousOption = 0;
// Go through different option numbers
for (CoAPOptionHeader header : options) {
int optionNumber = header.getOptionNumber();
//Determine delta and if needed additional deltas
int optionDelta = optionNumber - previousOption;
int additionalDelta1 = -1;
int additionalDelta2 = -1;
if (optionDelta >= ADDITIONAL_DELTA) {
if (optionDelta >= ADDITIONAL_DELTA_MAX) {
optionDelta -= ADDITIONAL_DELTA_MAX;
additionalDelta1 = getBitsInIntAsInt(optionDelta, 0, 8);
additionalDelta2 = getBitsInIntAsInt(optionDelta, 8, 8);
optionDelta = ADDITIONAL_DELTA_2;
} else {
additionalDelta1 = optionDelta - ADDITIONAL_DELTA;
optionDelta = ADDITIONAL_DELTA;
}
}
byte optionByte = setBitsInByte(0,
OPTION_DELTA_START, OPTION_DELTA_LENGTH, BitUtil
.getBitsInIntAsByte(optionDelta, 0,
OPTION_DELTA_LENGTH));
previousOption = optionNumber;
//Determine optionLength and if needed additional lengths
int optionLength = header.getLength();
int additionalLength1 = -1;
int additionalLength2 = -1;
if (optionLength >= ADDITIONAL_LENGTH) {
if (optionLength >= ADDITIONAL_LENGTH_MAX) {
optionLength -= ADDITIONAL_LENGTH_MAX;
additionalLength1 = getBitsInIntAsInt(optionLength, 0, 8);
additionalLength2 = getBitsInIntAsInt(optionLength, 8, 8);
optionLength = ADDITIONAL_LENGTH_2;
} else {
additionalLength1 = optionLength - ADDITIONAL_LENGTH;
optionLength = ADDITIONAL_LENGTH;
}
}
optionByte = setBitsInByte(optionByte,
OPTION_LENGTH_START, OPTION_LENGTH_LENGTH,
getBitsInIntAsByte(optionLength, 0,
OPTION_LENGTH_LENGTH));
outputStream.write(optionByte);
if (additionalDelta1 != -1) {
outputStream.write(additionalDelta1);
}
if (additionalDelta2 != -1) {
outputStream.write(additionalDelta2);
}
if (additionalLength1 != -1) {
outputStream.write(additionalLength1);
}
if (additionalLength2 != -1) {
outputStream.write(additionalLength2);
}
if (header.getLength() > 0) {
try {
outputStream.write(header.getValue());
} catch (IOException e) {
LOGGER.debug("Writing header failed.", e);
}
}
}
}
}