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