blob: 38524efabc3e9a0fa1b57babfc2285cb937493d9 [file] [log] [blame]
Andrew Browndb457052015-02-21 15:41:58 -08001/*
andrewsbrown4feb2da2015-03-03 16:05:29 -08002 * jndn-utils
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 Browndb457052015-02-21 15:41:58 -080013 */
14package com.intel.jndn.utils;
15
16import java.io.ByteArrayOutputStream;
17import java.io.IOException;
18import java.util.ArrayList;
19import java.util.List;
20import java.util.concurrent.ExecutionException;
andrewsbrown0cf35f92015-03-09 12:00:00 -070021import java.util.logging.Level;
Andrew Browndb457052015-02-21 15:41:58 -080022import java.util.logging.Logger;
23import net.named_data.jndn.Data;
24import net.named_data.jndn.Face;
25import net.named_data.jndn.Interest;
26import net.named_data.jndn.Name;
27import net.named_data.jndn.encoding.EncodingException;
28import net.named_data.jndn.util.Blob;
29
30/**
31 * Provide a client to simplify retrieving segmented Data packets over the NDN
32 * network. This class expects the Data producer to follow the NDN naming
33 * conventions (see http://named-data.net/doc/tech-memos/naming-conventions.pdf)
34 * and produce Data packets with a valid segment as the last component of their
35 * name; additionally, at least the first packet should set the FinalBlockId of
36 * the packet's MetaInfo (see
37 * http://named-data.net/doc/ndn-tlv/data.html#finalblockid).
38 *
39 * @author Andrew Brown <andrew.brown@intel.com>
40 */
41public class SegmentedClient {
42
43 private static SegmentedClient defaultInstance;
44 private static final Logger logger = Logger.getLogger(SegmentedClient.class.getName());
45 private static final int SLEEP_TIME_MS = 10;
46
47 /**
48 * Singleton access for simpler client use
49 *
50 * @return
51 */
52 public static SegmentedClient getDefault() {
53 if (defaultInstance == null) {
54 defaultInstance = new SegmentedClient();
55 }
56 return defaultInstance;
57 }
58
59 /**
60 * Asynchronously send Interest packets for a segmented result; will block
61 * until the first packet is received and then send remaining interests until
62 * the specified FinalBlockId.
63 *
64 * @param face
65 * @param interest should include either a ChildSelector or an initial segment
66 * number
andrewsbrown0cf35f92015-03-09 12:00:00 -070067 * @return a list of FutureData packets; if the first segment fails, the list
68 * will contain one FutureData with the failure exception
Andrew Browndb457052015-02-21 15:41:58 -080069 */
70 public List<FutureData> getAsync(Face face, Interest interest) {
71 // get first segment; default 0 or use a specified start segment
72 long firstSegment = 0;
73 boolean specifiedSegment = false;
74 try {
75 firstSegment = interest.getName().get(-1).toSegment();
76 specifiedSegment = true;
77 } catch (EncodingException e) {
78 // check for interest selector if no initial segment found
79 if (interest.getChildSelector() == -1) {
andrewsbrown0cf35f92015-03-09 12:00:00 -070080 logger.log(Level.WARNING, "No child selector set for a segmented Interest; this may result in incorrect retrieval.");
Andrew Browndb457052015-02-21 15:41:58 -080081 }
82 }
83
84 // setup segments
85 final List<FutureData> segments = new ArrayList<>();
86 segments.add(Client.getDefault().getAsync(face, interest));
87
88 // retrieve first packet to find last segment value
89 long lastSegment;
90 try {
91 lastSegment = segments.get(0).get().getMetaInfo().getFinalBlockId().toSegment();
92 } catch (ExecutionException | InterruptedException | EncodingException e) {
andrewsbrown0cf35f92015-03-09 12:00:00 -070093 logger.log(Level.SEVERE, "Failed to retrieve first segment: ", e);
94 return segments;
Andrew Browndb457052015-02-21 15:41:58 -080095 }
96
97 // cut interest segment off
98 if (specifiedSegment) {
99 interest.setName(interest.getName().getPrefix(-1));
100 }
101
102 // send interests in remaining segments
103 for (long i = firstSegment + 1; i <= lastSegment; i++) {
104 Interest segmentedInterest = new Interest(interest);
105 segmentedInterest.getName().appendSegment(i);
106 FutureData futureData = Client.getDefault().getAsync(face, segmentedInterest);
107 segments.add((int) i, futureData);
108 }
109
110 return segments;
111 }
112
113 /**
114 * Asynchronously send Interests for a segmented Data packet using a default
115 * interest (e.g. 2 second timeout); this will block until complete (i.e.
andrewsbrown0cf35f92015-03-09 12:00:00 -0700116 * either data is received or the interest times out). See getAsync(Face face)
117 * for more information.
Andrew Browndb457052015-02-21 15:41:58 -0800118 *
119 * @param face
120 * @param name
121 * @return
122 */
123 public List<FutureData> getAsync(Face face, Name name) {
124 return getAsync(face, Client.getDefaultInterest(name));
125 }
126
127 /**
128 * Retrieve a segmented Data packet; will block until all segments are
129 * received and will re-assemble these.
130 *
131 * @param face
132 * @param interest should include either a ChildSelector or an initial segment
133 * number
andrewsbrown0cf35f92015-03-09 12:00:00 -0700134 * @return a Data packet; the name will inherit from the sent Interest, not
135 * the returned packets (TODO or should we parse from returned Data? copy
136 * first Data?) and the content will be a concatenation of all of the packet
137 * contents.
Andrew Browndb457052015-02-21 15:41:58 -0800138 */
139 public Data getSync(Face face, Interest interest) {
140 List<FutureData> segments = getAsync(face, interest);
141
142 // process events until complete
143 while (!isSegmentListComplete(segments)) {
144 try {
145 face.processEvents();
146 Thread.sleep(SLEEP_TIME_MS);
147 } catch (EncodingException | IOException e) {
andrewsbrown0cf35f92015-03-09 12:00:00 -0700148 logger.log(Level.WARNING, "Failed to retrieve data: ", e);
Andrew Browndb457052015-02-21 15:41:58 -0800149 return null;
150 } catch (InterruptedException ex) {
151 // do nothing
152 }
153 }
154
155 // build final blob
156 ByteArrayOutputStream content = new ByteArrayOutputStream();
157 for (FutureData futureData : segments) {
158 try {
159 content.write(futureData.get().getContent().getImmutableArray());
160 } catch (ExecutionException | IOException | InterruptedException e) {
andrewsbrown0cf35f92015-03-09 12:00:00 -0700161 logger.log(Level.WARNING, "Failed to parse retrieved data: ", e);
Andrew Browndb457052015-02-21 15:41:58 -0800162 return null;
163 }
164 }
165
166 Data data = new Data(interest.getName()); // TODO this name may not be correct; may need to contain additional suffixes
167 data.setContent(new Blob(content.toByteArray()));
168 return data;
169 }
170
171 /**
172 * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
173 * second timeout); this will block until complete (i.e. either data is
andrewsbrown0cf35f92015-03-09 12:00:00 -0700174 * received or the interest times out). See getSync(Face face) for more
175 * information.
Andrew Browndb457052015-02-21 15:41:58 -0800176 *
177 * @param face
178 * @param name
179 * @return
180 */
181 public Data getSync(Face face, Name name) {
182 return getSync(face, Client.getDefaultInterest(name));
183 }
184
185 /**
186 * Check if a name ends in a segment component; uses marker value found in the
187 * NDN naming conventions (see
188 * http://named-data.net/doc/tech-memos/naming-conventions.pdf).
189 *
190 * @param name
191 * @return
192 */
193 public static boolean hasSegment(Name name) {
194 return name.get(-1).getValue().buf().get(0) == 0x00;
195 }
196
197 /**
198 * Check if a list of segments have returned from the network.
199 *
200 * @param segments
201 * @return
202 */
203 protected boolean isSegmentListComplete(List<FutureData> segments) {
204 for (FutureData futureData : segments) {
205 if (!futureData.isDone()) {
206 return false;
207 }
208 }
209 return true;
210 }
211}