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