001/**
002 *
003 * Copyright 2016 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.igniterealtime.smack.smackrepl;
018
019import java.io.IOException;
020import java.util.Collections;
021import java.util.List;
022import java.util.concurrent.TimeoutException;
023
024import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
025import org.jivesoftware.smack.SmackException;
026import org.jivesoftware.smack.XMPPException;
027import org.jivesoftware.smack.packet.Presence;
028import org.jivesoftware.smack.roster.Roster;
029import org.jivesoftware.smack.roster.RosterUtil;
030import org.jivesoftware.smack.tcp.XMPPTCPConnection;
031import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
032import org.jivesoftware.smack.util.StringUtils;
033
034import org.jivesoftware.smackx.iot.IoTDiscoveryIntegrationTest;
035import org.jivesoftware.smackx.iot.Thing;
036import org.jivesoftware.smackx.iot.data.IoTDataManager;
037import org.jivesoftware.smackx.iot.data.ThingMomentaryReadOutRequest;
038import org.jivesoftware.smackx.iot.data.ThingMomentaryReadOutResult;
039import org.jivesoftware.smackx.iot.data.element.IoTDataField;
040import org.jivesoftware.smackx.iot.data.element.IoTDataField.IntField;
041import org.jivesoftware.smackx.iot.data.element.IoTFieldsExtension;
042import org.jivesoftware.smackx.iot.discovery.AbstractThingStateChangeListener;
043import org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager;
044import org.jivesoftware.smackx.iot.discovery.ThingState;
045import org.jivesoftware.smackx.iot.provisioning.BecameFriendListener;
046import org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager;
047
048import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
049import org.jxmpp.jid.BareJid;
050import org.jxmpp.jid.EntityBareJid;
051import org.jxmpp.jid.impl.JidCreate;
052
053public class IoT {
054
055    // A 10 minute timeout.
056    private static final long TIMEOUT = 10 * 60 * 1000;
057
058    private interface IotScenario {
059        void iotScenario(XMPPTCPConnection dataThingConnection, XMPPTCPConnection readinThingConnection) throws XMPPException, SmackException, IOException, InterruptedException, TimeoutException, Exception;
060    }
061
062    public static void iotScenario(String dataThingJidString, String dataThingPassword, String readingThingJidString,
063            String readingThingPassword, IotScenario scenario) throws TimeoutException, Exception {
064        final EntityBareJid dataThingJid = JidCreate.entityBareFrom(dataThingJidString);
065        final EntityBareJid readingThingJid = JidCreate.entityBareFrom(readingThingJidString);
066
067        final XMPPTCPConnectionConfiguration dataThingConnectionConfiguration = XMPPTCPConnectionConfiguration.builder()
068                .setUsernameAndPassword(dataThingJid.getLocalpart(), dataThingPassword)
069                .setXmppDomain(dataThingJid.asDomainBareJid()).setSecurityMode(SecurityMode.disabled)
070                .setDebuggerEnabled(true).build();
071        final XMPPTCPConnectionConfiguration readingThingConnectionConfiguration = XMPPTCPConnectionConfiguration
072                .builder().setUsernameAndPassword(readingThingJid.getLocalpart(), readingThingPassword)
073                .setXmppDomain(readingThingJid.asDomainBareJid()).setSecurityMode(SecurityMode.disabled)
074                .setDebuggerEnabled(true).build();
075
076        final XMPPTCPConnection dataThingConnection = new XMPPTCPConnection(dataThingConnectionConfiguration);
077        final XMPPTCPConnection readingThingConnection = new XMPPTCPConnection(readingThingConnectionConfiguration);
078
079        dataThingConnection.setReplyTimeout(TIMEOUT);
080        readingThingConnection.setReplyTimeout(TIMEOUT);
081
082        dataThingConnection.setUseStreamManagement(false);
083        readingThingConnection.setUseStreamManagement(false);
084
085        try {
086            dataThingConnection.connect().login();
087            readingThingConnection.connect().login();
088            scenario.iotScenario(dataThingConnection, readingThingConnection);
089        } finally {
090            dataThingConnection.disconnect();
091            readingThingConnection.disconnect();
092        }
093    }
094
095    public static void iotReadOutScenario(String dataThingJidString, String dataThingPassword, String readingThingJidString,
096                    String readingThingPassword)
097                    throws Exception {
098        iotScenario(dataThingJidString, dataThingPassword, readingThingJidString, readingThingPassword, READ_OUT_SCENARIO);
099    }
100
101    public static final IotScenario READ_OUT_SCENARIO = new IotScenario() {
102        @Override
103        public void iotScenario(XMPPTCPConnection dataThingConnection, XMPPTCPConnection readingThingConnection) throws TimeoutException, Exception {
104            ThingState dataThingState = actAsDataThing(dataThingConnection);
105
106            final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint();
107            dataThingState.setThingStateChangeListener(new AbstractThingStateChangeListener() {
108                @Override
109                public void owned(BareJid jid) {
110                    syncPoint.signal();
111                }
112            });
113            // Wait until the thing is owned.
114            syncPoint.waitForResult(TIMEOUT);
115            printStatus("OWNED - Thing now onwed by " + dataThingState.getOwner());
116
117            // Make sure things are befriended.
118            IoTProvisioningManager readingThingProvisioningManager = IoTProvisioningManager.getInstanceFor(readingThingConnection);
119            readingThingProvisioningManager.sendFriendshipRequestIfRequired(dataThingConnection.getUser().asBareJid());
120
121            Roster dataThingRoster = Roster.getInstanceFor(dataThingConnection);
122            RosterUtil.waitUntilOtherEntityIsSubscribed(dataThingRoster, readingThingConnection.getUser().asBareJid(), TIMEOUT);
123            printStatus("FRIENDSHIP ACCEPTED - Trying to read out data");
124
125            IoTDataManager readingThingDataManager = IoTDataManager.getInstanceFor(readingThingConnection);
126            List<IoTFieldsExtension> values = readingThingDataManager.requestMomentaryValuesReadOut(dataThingConnection.getUser());
127            if (values.size() != 1) {
128                throw new IllegalStateException("Unexpected number of values returned: " + values.size());
129            }
130            IoTFieldsExtension field = values.get(0);
131            printStatus("DATA READ-OUT SUCCESS: " + field.toXML());
132            printStatus("IoT SCENARIO FINISHED SUCCESSFULLY");
133        }
134    };
135
136    public static void iotOwnerApprovesFriendScenario(String dataThingJidString, String dataThingPassword,
137            String readingThingJidString, String readingThingPassword) throws Exception {
138        iotScenario(dataThingJidString, dataThingPassword, readingThingJidString, readingThingPassword,
139                OWNER_APPROVES_FRIEND_SCENARIO);
140    }
141
142    public static final IotScenario OWNER_APPROVES_FRIEND_SCENARIO = new IotScenario() {
143        @Override
144        public void iotScenario(XMPPTCPConnection dataThingConnection, XMPPTCPConnection readingThingConnection) throws TimeoutException, Exception {
145            // First ensure that the two XMPP entities are not already subscribed to each other presences.
146            RosterUtil.ensureNotSubscribedToEachOther(dataThingConnection, readingThingConnection);
147
148            final BareJid dataThingBareJid = dataThingConnection.getUser().asBareJid();
149            final BareJid readingThingBareJid = readingThingConnection.getUser().asBareJid();
150            final ThingState dataThingState = actAsDataThing(dataThingConnection);
151
152            printStatus("WAITING for 'claimed' notification. Please claim thing now");
153            final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint();
154            dataThingState.setThingStateChangeListener(new AbstractThingStateChangeListener() {
155                @Override
156                public void owned(BareJid jid) {
157                    syncPoint.signal();
158                }
159            });
160            // Wait until the thing is owned.
161            syncPoint.waitForResult(TIMEOUT);
162            printStatus("OWNED - Thing now onwed by " + dataThingState.getOwner());
163
164            // Now, ReadingThing sends a friendship request to data thing, which
165            // will proxy the request to its provisioning service, which will
166            // likely return that both a not friends since the owner did not
167            // authorize the friendship yet.
168            final SimpleResultSyncPoint friendshipApprovedSyncPoint = new SimpleResultSyncPoint();
169            final IoTProvisioningManager readingThingProvisioningManager = IoTProvisioningManager.getInstanceFor(readingThingConnection);
170            final BecameFriendListener becameFriendListener = new BecameFriendListener() {
171                @Override
172                public void becameFriend(BareJid jid, Presence presence) {
173                    if (jid.equals(dataThingBareJid)) {
174                        friendshipApprovedSyncPoint.signal();
175                    }
176                }
177            };
178            readingThingProvisioningManager.addBecameFriendListener(becameFriendListener);
179
180            try {
181                readingThingProvisioningManager
182                        .sendFriendshipRequestIfRequired(dataThingConnection.getUser().asBareJid());
183                friendshipApprovedSyncPoint.waitForResult(TIMEOUT);
184            } finally {
185                readingThingProvisioningManager.removeBecameFriendListener(becameFriendListener);
186            }
187
188            printStatus("FRIENDSHIP APPROVED - ReadingThing " + readingThingBareJid + " is now a friend of DataThing " + dataThingBareJid);
189        }
190    };
191
192    private static ThingState actAsDataThing(XMPPTCPConnection connection) throws XMPPException, SmackException, InterruptedException {
193        final String key = StringUtils.randomString(12);
194        final String sn = StringUtils.randomString(12);
195        Thing dataThing = Thing.builder()
196                        .setKey(key)
197                        .setSerialNumber(sn)
198                        .setManufacturer("IgniteRealtime")
199                        .setModel("Smack")
200                        .setVersion("0.1")
201                        .setMomentaryReadOutRequestHandler(new ThingMomentaryReadOutRequest() {
202            @Override
203            public void momentaryReadOutRequest(ThingMomentaryReadOutResult callback) {
204                IoTDataField.IntField field = new IntField("timestamp", (int) (System.currentTimeMillis() / 1000));
205                callback.momentaryReadOut(Collections.singletonList(field));
206            }
207        })
208                        .build();
209        IoTDiscoveryManager iotDiscoveryManager = IoTDiscoveryManager.getInstanceFor(connection);
210        ThingState state = IoTDiscoveryIntegrationTest.registerThing(iotDiscoveryManager, dataThing);
211        printStatus("SUCCESS: Thing registered:" + dataThing);
212        return state;
213    }
214
215    private static void printStatus(CharSequence status) {
216        // CHECKSTYLE:OFF
217        System.out.println(status);
218        // CHECKSTYLE:ON
219    }
220
221    public static void main(String[] args) throws TimeoutException, Exception {
222        if (args.length != 4) {
223            throw new IllegalArgumentException();
224        }
225        iotOwnerApprovesFriendScenario(args[0], args[1], args[2], args[3]);
226    }
227
228}