/******************************************************************************* * Copyright (c) 2013-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: * Zebra Technologies - initial API and implementation * Achim Kraus (Bosch Software Innovations GmbH) - replace close() with destroy() *******************************************************************************/ package org.eclipse.leshan.integration.tests; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.*; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.californium.core.coap.CoAP.Type; import org.eclipse.californium.core.coap.OptionSet; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.californium.elements.RawData; import org.eclipse.leshan.ResponseCode; import org.eclipse.leshan.core.model.LwM2mModel; 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.LwM2mSingleResource; import org.eclipse.leshan.core.node.TimestampedLwM2mNode; import org.eclipse.leshan.core.node.codec.DefaultLwM2mValueConverter; import org.eclipse.leshan.core.node.codec.json.LwM2mNodeJsonEncoder; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.observation.ObservationListener; import org.eclipse.leshan.server.registration.Registration; import org.junit.After; import org.junit.Before; import org.junit.Test; public class ObserveTest { protected IntegrationTestHelper helper = new IntegrationTestHelper(); @Before public void start() { helper.initialize(); helper.createServer(); helper.server.start(); helper.createClient(); helper.client.start(); helper.waitForRegistration(1); } @After public void stop() { helper.client.destroy(false); helper.server.destroy(); helper.dispose(); } @Test public void can_observe_resource() throws InterruptedException { TestObservationListener listener = new TestObservationListener(); helper.server.getObservationService().addListener(listener); // observe device timezone ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3, 0, 15)); assertEquals(ResponseCode.CONTENT, observeResponse.getCode()); assertNotNull(observeResponse.getCoapResponse()); assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class))); // an observation response should have been sent Observation observation = observeResponse.getObservation(); assertEquals("/3/0/15", observation.getPath().toString()); assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId()); // write device timezone LwM2mResponse writeResponse = helper.server.send(helper.getCurrentRegistration(), new WriteRequest(3, 0, 15, "Europe/Paris")); // verify result listener.waitForNotification(2000); assertEquals(ResponseCode.CHANGED, writeResponse.getCode()); assertTrue(listener.receivedNotify().get()); assertEquals(LwM2mSingleResource.newStringResource(15, "Europe/Paris"), listener.getResponse().getContent()); assertNotNull(listener.getResponse().getCoapResponse()); assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class))); } @Test public void can_observe_instance() throws InterruptedException { TestObservationListener listener = new TestObservationListener(); helper.server.getObservationService().addListener(listener); // observe device timezone ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3, 0)); assertEquals(ResponseCode.CONTENT, observeResponse.getCode()); assertNotNull(observeResponse.getCoapResponse()); assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class))); // an observation response should have been sent Observation observation = observeResponse.getObservation(); assertEquals("/3/0", observation.getPath().toString()); assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId()); // write device timezone LwM2mResponse writeResponse = helper.server.send(helper.getCurrentRegistration(), new WriteRequest(3, 0, 15, "Europe/Paris")); // verify result listener.waitForNotification(2000); assertEquals(ResponseCode.CHANGED, writeResponse.getCode()); assertTrue(listener.receivedNotify().get()); assertTrue(listener.getResponse().getContent() instanceof LwM2mObjectInstance); assertNotNull(listener.getResponse().getCoapResponse()); assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class))); // try to read the object instance for comparing ReadResponse readResp = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3, 0)); assertEquals(readResp.getContent(), listener.getResponse().getContent()); } @Test public void can_observe_object() throws InterruptedException { TestObservationListener listener = new TestObservationListener(); helper.server.getObservationService().addListener(listener); // observe device timezone ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3)); assertEquals(ResponseCode.CONTENT, observeResponse.getCode()); assertNotNull(observeResponse.getCoapResponse()); assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class))); // an observation response should have been sent Observation observation = observeResponse.getObservation(); assertEquals("/3", observation.getPath().toString()); assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId()); // write device timezone LwM2mResponse writeResponse = helper.server.send(helper.getCurrentRegistration(), new WriteRequest(3, 0, 15, "Europe/Paris")); // verify result listener.waitForNotification(2000); assertEquals(ResponseCode.CHANGED, writeResponse.getCode()); assertTrue(listener.receivedNotify().get()); assertTrue(listener.getResponse().getContent() instanceof LwM2mObject); assertNotNull(listener.getResponse().getCoapResponse()); assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class))); // try to read the object for comparing ReadResponse readResp = helper.server.send(helper.getCurrentRegistration(), new ReadRequest(3)); assertEquals(readResp.getContent(), listener.getResponse().getContent()); } @Test public void can_handle_error_on_notification() throws InterruptedException { TestObservationListener listener = new TestObservationListener(); helper.server.getObservationService().addListener(listener); // observe device timezone ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3, 0, 15)); assertEquals(ResponseCode.CONTENT, observeResponse.getCode()); assertNotNull(observeResponse.getCoapResponse()); assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class))); // an observation response should have been sent Observation observation = observeResponse.getObservation(); assertEquals("/3/0/15", observation.getPath().toString()); assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId()); // *** HACK send a notification with unsupported content format *** // byte[] payload = LwM2mNodeJsonEncoder.encode(LwM2mSingleResource.newStringResource(15, "Paris"), new LwM2mPath("/3/0/15"), new LwM2mModel(helper.createObjectModels()), new DefaultLwM2mValueConverter()); Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); sendNotification(payload, firstCoapResponse, 666); // 666 is not a supported contentFormat. // *** Hack End *** // // verify result listener.waitForNotification(2000); assertTrue(listener.receivedNotify().get()); assertNotNull(listener.getError()); } @Test public void can_observe_timestamped_resource() throws InterruptedException { TestObservationListener listener = new TestObservationListener(); helper.server.getObservationService().addListener(listener); // observe device timezone ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3, 0, 15)); assertEquals(ResponseCode.CONTENT, observeResponse.getCode()); assertNotNull(observeResponse.getCoapResponse()); assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class))); // an observation response should have been sent Observation observation = observeResponse.getObservation(); assertEquals("/3/0/15", observation.getPath().toString()); assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId()); // *** HACK send time-stamped notification as Leshan client does not support it *** // // create time-stamped nodes TimestampedLwM2mNode mostRecentNode = new TimestampedLwM2mNode(System.currentTimeMillis(), LwM2mSingleResource.newStringResource(15, "Paris")); List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>(); timestampedNodes.add(mostRecentNode); timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2, LwM2mSingleResource.newStringResource(15, "Londres"))); byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0/15"), new LwM2mModel(helper.createObjectModels()), new DefaultLwM2mValueConverter()); Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); sendNotification(payload, firstCoapResponse, ContentFormat.JSON_CODE); // *** Hack End *** // // verify result listener.waitForNotification(2000); assertTrue(listener.receivedNotify().get()); assertEquals(mostRecentNode.getNode(), listener.getResponse().getContent()); assertEquals(timestampedNodes, listener.getResponse().getTimestampedLwM2mNode()); assertNotNull(listener.getResponse().getCoapResponse()); assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class))); } @Test public void can_observe_timestamped_instance() throws InterruptedException { TestObservationListener listener = new TestObservationListener(); helper.server.getObservationService().addListener(listener); // observe device timezone ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3, 0)); assertEquals(ResponseCode.CONTENT, observeResponse.getCode()); assertNotNull(observeResponse.getCoapResponse()); assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class))); // an observation response should have been sent Observation observation = observeResponse.getObservation(); assertEquals("/3/0", observation.getPath().toString()); assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId()); // *** HACK send time-stamped notification as Leshan client does not support it *** // // create time-stamped nodes TimestampedLwM2mNode mostRecentNode = new TimestampedLwM2mNode(System.currentTimeMillis(), new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Paris"))); List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>(); timestampedNodes.add(mostRecentNode); timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2, new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres")))); byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3/0"), new LwM2mModel(helper.createObjectModels()), new DefaultLwM2mValueConverter()); Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); sendNotification(payload, firstCoapResponse, ContentFormat.JSON_CODE); // *** Hack End *** // // verify result listener.waitForNotification(2000); assertTrue(listener.receivedNotify().get()); assertEquals(mostRecentNode.getNode(), listener.getResponse().getContent()); assertEquals(timestampedNodes, listener.getResponse().getTimestampedLwM2mNode()); assertNotNull(listener.getResponse().getCoapResponse()); assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class))); } @Test public void can_observe_timestamped_object() throws InterruptedException { TestObservationListener listener = new TestObservationListener(); helper.server.getObservationService().addListener(listener); // observe device timezone ObserveResponse observeResponse = helper.server.send(helper.getCurrentRegistration(), new ObserveRequest(3)); assertEquals(ResponseCode.CONTENT, observeResponse.getCode()); assertNotNull(observeResponse.getCoapResponse()); assertThat(observeResponse.getCoapResponse(), is(instanceOf(Response.class))); // an observation response should have been sent Observation observation = observeResponse.getObservation(); assertEquals("/3", observation.getPath().toString()); assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId()); // *** HACK send time-stamped notification as Leshan client does not support it *** // // create time-stamped nodes TimestampedLwM2mNode mostRecentNode = new TimestampedLwM2mNode(System.currentTimeMillis(), new LwM2mObject(3, new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Paris")))); List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>(); timestampedNodes.add(mostRecentNode); timestampedNodes.add(new TimestampedLwM2mNode(mostRecentNode.getTimestamp() - 2, new LwM2mObject(3, new LwM2mObjectInstance(0, LwM2mSingleResource.newStringResource(15, "Londres"))))); byte[] payload = LwM2mNodeJsonEncoder.encodeTimestampedData(timestampedNodes, new LwM2mPath("/3"), new LwM2mModel(helper.createObjectModels()), new DefaultLwM2mValueConverter()); Response firstCoapResponse = (Response) observeResponse.getCoapResponse(); sendNotification(payload, firstCoapResponse, ContentFormat.JSON_CODE); // *** Hack End *** // // verify result listener.waitForNotification(2000); assertTrue(listener.receivedNotify().get()); assertEquals(mostRecentNode.getNode(), listener.getResponse().getContent()); assertEquals(timestampedNodes, listener.getResponse().getTimestampedLwM2mNode()); assertNotNull(listener.getResponse().getCoapResponse()); assertThat(listener.getResponse().getCoapResponse(), is(instanceOf(Response.class))); } private void sendNotification(byte[] payload, Response firstCoapResponse, int contentFormat) { // encode and send it try (DatagramSocket clientSocket = new DatagramSocket()) { // create observe response Response response = new Response(org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT); response.setType(Type.NON); response.setPayload(payload); response.setMID(firstCoapResponse.getMID() + 1); response.setToken(firstCoapResponse.getToken()); OptionSet options = new OptionSet().setContentFormat(contentFormat) .setObserve(firstCoapResponse.getOptions().getObserve() + 1); response.setOptions(options); // serialize response UdpDataSerializer serializer = new UdpDataSerializer(); RawData data = serializer.serializeResponse(response); // send it clientSocket.send(new DatagramPacket(data.bytes, data.bytes.length, helper.server.getNonSecureAddress().getAddress(), helper.server.getNonSecureAddress().getPort())); } catch (IOException e) { throw new AssertionError("Error while timestamped notification", e); } } private final class TestObservationListener implements ObservationListener { private final CountDownLatch latch = new CountDownLatch(1); private final AtomicBoolean receivedNotify = new AtomicBoolean(); private ObserveResponse response; private Exception error; @Override public void onResponse(Observation observation, Registration registration, ObserveResponse response) { receivedNotify.set(true); this.response = response; this.error = null; latch.countDown(); } @Override public void onError(Observation observation, Registration registration, Exception error) { receivedNotify.set(true); this.response = null; this.error = error; latch.countDown(); } @Override public void cancelled(Observation observation) { latch.countDown(); } @Override public void newObservation(Observation observation, Registration registration) { } public AtomicBoolean receivedNotify() { return receivedNotify; } public ObserveResponse getResponse() { return response; } public Exception getError() { return error; } public void waitForNotification(long timeout) throws InterruptedException { latch.await(timeout, TimeUnit.MILLISECONDS); } } }