/*******************************************************************************
* Copyright (c) 2015 Sierra Wireless and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.eclipse.leshan.core.node.codec.tlv;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.Map.Entry;
import org.eclipse.leshan.core.model.LwM2mModel;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mNodeVisitor;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.node.codec.LwM2mValueConverter;
import org.eclipse.leshan.tlv.Tlv;
import org.eclipse.leshan.tlv.Tlv.TlvType;
import org.eclipse.leshan.tlv.TlvEncoder;
import org.eclipse.leshan.util.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TLV encoder for {@link LwM2mNode}.
*/
public class LwM2mNodeTlvEncoder {
private static final Logger LOG = LoggerFactory.getLogger(LwM2mNodeTlvEncoder.class);
public static byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, LwM2mValueConverter converter)
throws CodecException {
Validate.notNull(node);
Validate.notNull(path);
Validate.notNull(model);
InternalEncoder internalEncoder = new InternalEncoder();
internalEncoder.path = path;
internalEncoder.model = model;
internalEncoder.converter = converter;
node.accept(internalEncoder);
return internalEncoder.out.toByteArray();
}
private static class InternalEncoder implements LwM2mNodeVisitor {
// visitor inputs
private LwM2mPath path;
private LwM2mModel model;
private LwM2mValueConverter converter;
// visitor output
private ByteArrayOutputStream out = new ByteArrayOutputStream();
@Override
public void visit(LwM2mObject object) {
LOG.trace("Encoding object {} into TLV", object);
Tlv[] tlvs;
ObjectModel objectModel = model.getObjectModel(object.getId());
if (objectModel != null && !objectModel.multiple) {
// single instance object, the instance is level is not needed
tlvs = encodeResources(object.getInstance(0).getResources().values(), new LwM2mPath(object.getId(), 0));
} else {
// encoded as an array of instances
tlvs = new Tlv[object.getInstances().size()];
int i = 0;
for (Entry<Integer, LwM2mObjectInstance> instance : object.getInstances().entrySet()) {
Tlv[] resources = encodeResources(instance.getValue().getResources().values(),
new LwM2mPath(object.getId(), instance.getKey()));
tlvs[i] = new Tlv(TlvType.OBJECT_INSTANCE, resources, null, instance.getKey());
i++;
}
}
try {
out.write(TlvEncoder.encode(tlvs).array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(LwM2mObjectInstance instance) {
LOG.trace("Encoding object instance {} into TLV", instance);
Tlv[] tlvs;
if (path.isObjectInstance() || instance.getId() == LwM2mObjectInstance.UNDEFINED) {
// the instanceId is part of the request path or is undefined
// so the instance TLV layer is not needed.
// encoded as an array of resource TLVs
tlvs = encodeResources(instance.getResources().values(),
new LwM2mPath(path.getObjectId(), instance.getId()));
} else {
// encoded as an instance TLV
Tlv[] resources = encodeResources(instance.getResources().values(),
new LwM2mPath(path.getObjectId(), instance.getId()));
tlvs = new Tlv[] { new Tlv(TlvType.OBJECT_INSTANCE, resources, null, instance.getId()) };
}
try {
out.write(TlvEncoder.encode(tlvs).array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void visit(LwM2mResource resource) {
LOG.trace("Encoding resource {} into TLV", resource);
Tlv rTlv = encodeResource(resource, path);
try {
out.write(TlvEncoder.encode(new Tlv[] { rTlv }).array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Tlv[] encodeResources(Collection<LwM2mResource> resources, LwM2mPath instancePath) {
Tlv[] rTlvs = new Tlv[resources.size()];
int i = 0;
for (LwM2mResource resource : resources) {
rTlvs[i] = encodeResource(resource, instancePath.append(resource.getId()));
i++;
}
return rTlvs;
}
private Tlv encodeResource(LwM2mResource resource, LwM2mPath resourcePath) {
ResourceModel rSpec = model.getResourceModel(path.getObjectId(), resource.getId());
Type expectedType = rSpec != null ? rSpec.type : resource.getType();
Tlv rTlv;
if (resource.isMultiInstances()) {
Tlv[] instances = new Tlv[resource.getValues().size()];
int i = 0;
for (Entry<Integer, ?> entry : resource.getValues().entrySet()) {
LwM2mPath resourceInstancePath = resourcePath.append(entry.getKey());
Object convertedValue = converter.convertValue(entry.getValue(), resource.getType(), expectedType,
resourceInstancePath);
instances[i] = new Tlv(TlvType.RESOURCE_INSTANCE, null,
this.encodeTlvValue(convertedValue, expectedType, resourceInstancePath), entry.getKey());
i++;
}
rTlv = new Tlv(TlvType.MULTIPLE_RESOURCE, instances, null, resource.getId());
} else {
Object convertedValue = converter.convertValue(resource.getValue(), resource.getType(), expectedType,
resourcePath);
rTlv = new Tlv(TlvType.RESOURCE_VALUE, null,
this.encodeTlvValue(convertedValue, expectedType, resourcePath), resource.getId());
}
return rTlv;
}
private byte[] encodeTlvValue(Object value, Type type, LwM2mPath path) {
LOG.trace("Encoding value {} in TLV", value);
try {
switch (type) {
case STRING:
return TlvEncoder.encodeString((String) value);
case INTEGER:
return TlvEncoder.encodeInteger((Number) value);
case FLOAT:
return TlvEncoder.encodeFloat((Number) value);
case BOOLEAN:
return TlvEncoder.encodeBoolean((Boolean) value);
case TIME:
return TlvEncoder.encodeDate((Date) value);
case OPAQUE:
return (byte[]) value;
case OBJLNK:
return TlvEncoder.encodeObjlnk((ObjectLink) value);
default:
throw new CodecException("Invalid value %s for type %s of %s", value, type, path);
}
} catch (IllegalArgumentException e) {
throw new CodecException(e, "Invalid value %s for type %s of %s", value, type, path);
}
}
}
}