Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 1 | /* |
andrewsbrown | 533c6ef | 2015-03-03 16:08:41 -0800 | [diff] [blame] | 2 | * jndn-mock |
| 3 | * Copyright (c) 2015, Intel Corporation. |
| 4 | * |
| 5 | * This program is free software; you can redistribute it and/or modify it |
| 6 | * under the terms and conditions of the GNU Lesser General Public License, |
| 7 | * version 3, as published by the Free Software Foundation. |
| 8 | * |
| 9 | * This program is distributed in the hope it will be useful, but WITHOUT ANY |
| 10 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 11 | * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for |
| 12 | * more details. |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 13 | */ |
| 14 | package com.intel.jndn.mock; |
| 15 | |
| 16 | import java.io.IOException; |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 17 | import java.nio.ByteBuffer; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 18 | import java.util.HashMap; |
| 19 | import java.util.Map.Entry; |
Andrew Brown | ec1b0d0 | 2015-02-21 13:11:42 -0800 | [diff] [blame] | 20 | import java.util.logging.Logger; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 21 | import net.named_data.jndn.Data; |
Andrew Brown | b91e690 | 2015-02-12 09:01:50 -0800 | [diff] [blame] | 22 | import net.named_data.jndn.Face; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 23 | import net.named_data.jndn.ForwardingFlags; |
| 24 | import net.named_data.jndn.Interest; |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 25 | import net.named_data.jndn.InterestFilter; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 26 | import net.named_data.jndn.Name; |
| 27 | import net.named_data.jndn.Node; |
| 28 | import net.named_data.jndn.OnData; |
| 29 | import net.named_data.jndn.OnInterest; |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 30 | import net.named_data.jndn.OnInterestCallback; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 31 | import net.named_data.jndn.OnRegisterFailed; |
| 32 | import net.named_data.jndn.OnTimeout; |
| 33 | import net.named_data.jndn.encoding.EncodingException; |
| 34 | import net.named_data.jndn.encoding.WireFormat; |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 35 | import net.named_data.jndn.security.SecurityException; |
andrewsbrown | ebb72a8 | 2015-03-31 13:31:18 -0700 | [diff] [blame] | 36 | import net.named_data.jndn.transport.Transport; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 37 | |
| 38 | /** |
Andrew Brown | b91e690 | 2015-02-12 09:01:50 -0800 | [diff] [blame] | 39 | * <p> |
| 40 | * Use the MockTransport to mock sending data over the network. Allows for |
| 41 | * testing NDN applications while simulating network IO. TODO implement longest |
| 42 | * prefix match here for comprehensive testing. |
| 43 | * </p> |
| 44 | * <p> |
| 45 | * Usage |
| 46 | * </p> |
| 47 | * <pre><code> |
| 48 | * Face mockFace = new MockFace(); |
| 49 | * mockFace.registerPrefix(...); // as usual |
| 50 | * mockFace.expressInterest(...); // as usual |
| 51 | * |
| 52 | * // also, simply inject a response that will be returned for an expressed interest |
| 53 | * mockFace.addResponse(interestName, data); |
| 54 | * </pre></code> |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 55 | * |
| 56 | * @author Andrew Brown <andrew.brown@intel.com> |
| 57 | */ |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 58 | public class MockFace extends FaceExtension { |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 59 | |
Andrew Brown | ec1b0d0 | 2015-02-21 13:11:42 -0800 | [diff] [blame] | 60 | private static final Logger logger = Logger.getLogger(MockFace.class.getName()); |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 61 | private final Node node_; |
| 62 | HashMap<String, Data> responseMap = new HashMap<>(); |
| 63 | HashMap<Long, MockOnInterestHandler> handlerMap = new HashMap<>(); |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 64 | long lastPendingInterestId = 0; |
| 65 | long lastInterestFilterId = 0; |
| 66 | long lastRegisteredPrefixId = 0; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 67 | |
| 68 | /** |
| 69 | * Create a new Face to mock communication over the network; all packets are |
| 70 | * maintained in memory |
| 71 | */ |
| 72 | public MockFace() { |
| 73 | node_ = new Node(new MockTransport(), null); |
| 74 | } |
| 75 | |
| 76 | /** |
andrewsbrown | ebb72a8 | 2015-03-31 13:31:18 -0700 | [diff] [blame] | 77 | * @return a reference to the current MockTransport |
| 78 | */ |
| 79 | public MockTransport getTransport() { |
| 80 | return (MockTransport) node_.getTransport(); |
| 81 | } |
| 82 | |
| 83 | /** |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 84 | * Add a response Data packet to send immediately when an Interest with a |
| 85 | * matching name is received; will continue to respond with the same packet |
| 86 | * over multiple requests. This will preempt any registered OnInterest |
| 87 | * handlers. |
| 88 | * |
| 89 | * @param name |
| 90 | * @param data |
| 91 | */ |
| 92 | public void addResponse(Name name, Data data) { |
Andrew Brown | ec1b0d0 | 2015-02-21 13:11:42 -0800 | [diff] [blame] | 93 | logger.fine("Added response for: " + name.toUri()); |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 94 | responseMap.put(name.toUri(), data); |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Stop sending a response for the given name. |
| 99 | * |
| 100 | * @param name |
| 101 | */ |
| 102 | public void removeResponse(Name name) { |
Andrew Brown | ec1b0d0 | 2015-02-21 13:11:42 -0800 | [diff] [blame] | 103 | logger.fine("Removed response for: " + name.toUri()); |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 104 | responseMap.remove(name); |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Handle incoming Interest packets; when an Interest is expressed through |
| 109 | * expressInterest(), this will run to determine if: 1) any responses have |
Andrew Brown | 8d8535b | 2015-01-19 15:22:06 -0800 | [diff] [blame] | 110 | * been registered or 2) if any OnInterest handlers have been registered. If |
| 111 | * one of these two succeeds, this method then re-directs the Interest from |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 112 | * traveling down the network stack and returns data. |
Andrew Brown | 8d8535b | 2015-01-19 15:22:06 -0800 | [diff] [blame] | 113 | * |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 114 | * @param interest |
| 115 | */ |
| 116 | protected void handleIncomingRequests(Interest interest) { |
| 117 | String interestName = interest.getName().toUri(); |
| 118 | long registeredPrefixId = findRegisteredHandler(interest); |
| 119 | // check if response registered |
| 120 | if (responseMap.containsKey(interestName)) { |
Andrew Brown | ec1b0d0 | 2015-02-21 13:11:42 -0800 | [diff] [blame] | 121 | logger.fine("Found response for: " + interestName); |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 122 | Data data = responseMap.get(interestName); |
| 123 | ((MockTransport) node_.getTransport()).respondWith(data); |
Andrew Brown | 8d8535b | 2015-01-19 15:22:06 -0800 | [diff] [blame] | 124 | } // check if handler registered |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 125 | else if (registeredPrefixId != -1) { |
Andrew Brown | ec1b0d0 | 2015-02-21 13:11:42 -0800 | [diff] [blame] | 126 | logger.fine("Found handler for: " + interestName); |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 127 | MockOnInterestHandler handler = handlerMap.get(findRegisteredHandler(interest)); |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 128 | handler.signal(interest, registeredPrefixId); |
Andrew Brown | 8d8535b | 2015-01-19 15:22:06 -0800 | [diff] [blame] | 129 | } // log failure |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 130 | else { |
Andrew Brown | ec1b0d0 | 2015-02-21 13:11:42 -0800 | [diff] [blame] | 131 | logger.warning("No response found for interest (aborting): " + interestName); |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 132 | } |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Find a handler that matches the incoming interest; currently, the only |
| 137 | * flags supported are the ChildInherit flags. |
Andrew Brown | 8d8535b | 2015-01-19 15:22:06 -0800 | [diff] [blame] | 138 | * |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 139 | * @param interest |
Andrew Brown | 8d8535b | 2015-01-19 15:22:06 -0800 | [diff] [blame] | 140 | * @return |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 141 | */ |
| 142 | protected long findRegisteredHandler(Interest interest) { |
| 143 | for (Entry<Long, MockOnInterestHandler> entry : handlerMap.entrySet()) { |
| 144 | MockOnInterestHandler handler = entry.getValue(); |
| 145 | if (handler.flags.getChildInherit() && handler.prefix.match(interest.getName())) { |
| 146 | return entry.getKey(); |
| 147 | } |
| 148 | if (handler.prefix.equals(interest.getName())) { |
| 149 | return entry.getKey(); |
| 150 | } |
| 151 | } |
| 152 | return -1; |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * Helper class for holding references to OnInterest handlers |
| 157 | */ |
| 158 | class MockOnInterestHandler { |
| 159 | |
| 160 | Name prefix; |
| 161 | OnInterest onInterest; |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 162 | OnInterestCallback onInterestCallback; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 163 | ForwardingFlags flags; |
| 164 | |
| 165 | public MockOnInterestHandler(Name prefix, OnInterest onInterest, ForwardingFlags flags) { |
| 166 | this.prefix = prefix; |
| 167 | this.onInterest = onInterest; |
| 168 | this.flags = flags; |
| 169 | } |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 170 | |
| 171 | public MockOnInterestHandler(Name prefix, OnInterestCallback onInterestCallback, ForwardingFlags flags) { |
| 172 | this.prefix = prefix; |
| 173 | this.onInterestCallback = onInterestCallback; |
| 174 | this.flags = flags; |
| 175 | } |
| 176 | |
| 177 | public void signal(Interest interest, long registeredPrefixId){ |
| 178 | if(onInterest != null){ |
| 179 | onInterest.onInterest(prefix, interest, node_.getTransport(), registeredPrefixId); |
| 180 | } |
| 181 | if(onInterestCallback != null){ |
| 182 | onInterestCallback.onInterest(prefix, interest, MockFace.this, registeredPrefixId, null); |
| 183 | } |
| 184 | } |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Send the Interest through the transport, read the entire response and call |
| 189 | * onData(interest, data). |
| 190 | * |
| 191 | * @param interest The Interest to send. This copies the Interest. |
| 192 | * @param onData When a matching data packet is received, this calls |
| 193 | * onData.onData(interest, data) where interest is the interest given to |
| 194 | * expressInterest and data is the received Data object. NOTE: You must not |
| 195 | * change the interest object - if you need to change it then make a copy. |
| 196 | * @param onTimeout If the interest times out according to the interest |
| 197 | * lifetime, this calls onTimeout.onTimeout(interest) where interest is the |
| 198 | * interest given to expressInterest. If onTimeout is null, this does not use |
| 199 | * it. |
| 200 | * @param wireFormat A WireFormat object used to encode the message. |
| 201 | * @return The pending interest ID which can be used with |
| 202 | * removePendingInterest. |
| 203 | * @throws IOException For I/O error in sending the interest. |
| 204 | */ |
Andrew Brown | b91e690 | 2015-02-12 09:01:50 -0800 | [diff] [blame] | 205 | @Override |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 206 | public long expressInterest(Interest interest, OnData onData, OnTimeout onTimeout, |
| 207 | WireFormat wireFormat) throws IOException { |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 208 | long id = lastPendingInterestId++; |
| 209 | node_.expressInterest(id, interest, onData, onTimeout, wireFormat, this); |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 210 | handleIncomingRequests(interest); |
| 211 | return id; |
| 212 | } |
| 213 | |
| 214 | /** |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 215 | * Register prefix with the connected NDN hub and call onInterest when a |
| 216 | * matching interest is received. If you have not called |
| 217 | * setCommandSigningInfo, this assumes you are connecting to NDNx. If you have |
| 218 | * called setCommandSigningInfo, this first sends an NFD registration request, |
| 219 | * and if that times out then this sends an NDNx registration request. If you |
| 220 | * need to register a prefix with NFD, you must first call |
| 221 | * setCommandSigningInfo. |
| 222 | * |
| 223 | * @param prefix A Name for the prefix to register. This copies the Name. |
| 224 | * @param onInterest When an interest is received which matches the name |
| 225 | * prefix, this calls onInterest.onInterest(prefix, interest, transport, |
| 226 | * registeredPrefixId). NOTE: You must not change the prefix object - if you |
| 227 | * need to change it then make a copy. |
| 228 | * @param onRegisterFailed If register prefix fails for any reason, this calls |
| 229 | * onRegisterFailed.onRegisterFailed(prefix). |
| 230 | * @param flags The flags for finer control of which interests are forwarded |
| 231 | * to the application. |
| 232 | * @param wireFormat A WireFormat object used to encode the message. |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 233 | * @return The lastRegisteredPrefixId prefix ID which can be used with |
| 234 | removeRegisteredPrefix. |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 235 | * @throws IOException For I/O error in sending the registration request. |
| 236 | * @throws SecurityException If signing a command interest for NFD and cannot |
| 237 | * find the private key for the certificateName. |
| 238 | */ |
Andrew Brown | b91e690 | 2015-02-12 09:01:50 -0800 | [diff] [blame] | 239 | @Override |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 240 | public long registerPrefix(Name prefix, OnInterest onInterest, OnRegisterFailed onRegisterFailed, |
| 241 | ForwardingFlags flags, WireFormat wireFormat) throws IOException, net.named_data.jndn.security.SecurityException { |
andrewsbrown | a52bf7d | 2015-04-06 13:51:53 -0700 | [diff] [blame] | 242 | // since we don't send an Interest, ensure the transport is connected |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 243 | if (!getTransport().getIsConnected()) { |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 244 | getTransport().connect(node_.getConnectionInfo(), node_, null); |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 245 | } |
| 246 | |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 247 | lastRegisteredPrefixId++; |
| 248 | handlerMap.put(lastRegisteredPrefixId, new MockOnInterestHandler(prefix, onInterest, flags)); |
| 249 | return lastRegisteredPrefixId; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 250 | } |
| 251 | |
Andrew Brown | b91e690 | 2015-02-12 09:01:50 -0800 | [diff] [blame] | 252 | @Override |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 253 | public long registerPrefix(Name prefix, OnInterestCallback onInterest, OnRegisterFailed onRegisterFailed, ForwardingFlags flags, WireFormat wireFormat) throws IOException, SecurityException { |
| 254 | // since we don't send an Interest, ensure the transport is connected |
| 255 | if (!getTransport().getIsConnected()) { |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 256 | getTransport().connect(node_.getConnectionInfo(), node_, null); |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 257 | } |
| 258 | |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 259 | lastRegisteredPrefixId++; |
| 260 | handlerMap.put(lastRegisteredPrefixId, new MockOnInterestHandler(prefix, onInterest, flags)); |
| 261 | return lastRegisteredPrefixId; |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 262 | } |
| 263 | |
| 264 | /** |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 265 | * Process any packets to receive and call callbacks such as onData, |
| 266 | * onInterest or onTimeout. This returns immediately if there is no data to |
| 267 | * receive. This blocks while calling the callbacks. You should repeatedly |
| 268 | * call this from an event loop, with calls to sleep as needed so that the |
| 269 | * loop doesn’t use 100% of the CPU. Since processEvents modifies the pending |
| 270 | * interest table, your application should make sure that it calls |
| 271 | * processEvents in the same thread as expressInterest (which also modifies |
| 272 | * the pending interest table). This may throw an exception for reading data |
| 273 | * or in the callback for processing the data. If you call this from an main |
| 274 | * event loop, you may want to catch and log/disregard all exceptions. |
| 275 | */ |
Andrew Brown | b91e690 | 2015-02-12 09:01:50 -0800 | [diff] [blame] | 276 | @Override |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 277 | public void processEvents() throws IOException, EncodingException { |
| 278 | // Just call Node's processEvents. |
| 279 | node_.processEvents(); |
| 280 | } |
| 281 | |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 282 | @Override |
| 283 | public void removeRegisteredPrefix(long registeredPrefixId) { |
| 284 | handlerMap.remove(registeredPrefixId); |
| 285 | } |
| 286 | |
| 287 | @Override |
| 288 | public long setInterestFilter(InterestFilter filter, OnInterestCallback onInterest) { |
andrewsbrown | 2304a34 | 2015-09-16 20:09:53 +0100 | [diff] [blame] | 289 | long id = lastInterestFilterId++; |
| 290 | node_.setInterestFilter(id, filter, onInterest, this); |
| 291 | return id; |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 292 | } |
| 293 | |
| 294 | @Override |
| 295 | public void unsetInterestFilter(long interestFilterId) { |
andrewsbrown | 0f36eee | 2015-05-07 01:37:48 +0100 | [diff] [blame] | 296 | node_.unsetInterestFilter(interestFilterId); |
andrewsbrown | 1f28bcf | 2015-04-20 13:29:20 -0700 | [diff] [blame] | 297 | } |
| 298 | |
| 299 | @Override |
| 300 | public void putData(Data data, WireFormat wireFormat) throws IOException { |
| 301 | node_.putData(data, wireFormat); |
| 302 | } |
| 303 | |
| 304 | @Override |
| 305 | public void send(ByteBuffer encoding) throws IOException { |
| 306 | node_.send(encoding); |
| 307 | } |
| 308 | |
| 309 | @Override |
| 310 | public boolean isLocal() throws IOException { |
| 311 | return true; |
| 312 | } |
| 313 | |
| 314 | @Override |
| 315 | public void shutdown() { |
| 316 | node_.shutdown(); |
| 317 | } |
| 318 | |
Andrew Brown | 3831baf | 2015-01-19 13:38:52 -0800 | [diff] [blame] | 319 | } |