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