diff options
Diffstat (limited to 'src/fs/fs_tree.c')
-rw-r--r-- | src/fs/fs_tree.c | 250 |
1 files changed, 139 insertions, 111 deletions
diff --git a/src/fs/fs_tree.c b/src/fs/fs_tree.c index 05c9c55cd..e22ce8221 100644 --- a/src/fs/fs_tree.c +++ b/src/fs/fs_tree.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /* | 1 | /* |
2 | This file is part of GNUnet. | 2 | This file is part of GNUnet. |
3 | (C) 2009 Christian Grothoff (and other contributing authors) | 3 | (C) 2009-2011 Christian Grothoff (and other contributing authors) |
4 | 4 | ||
5 | GNUnet is free software; you can redistribute it and/or modify | 5 | GNUnet is free software; you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published | 6 | it under the terms of the GNU General Public License as published |
@@ -75,7 +75,7 @@ struct GNUNET_FS_TreeEncoder | |||
75 | * Set to the URI (upon successful completion) | 75 | * Set to the URI (upon successful completion) |
76 | */ | 76 | */ |
77 | struct GNUNET_FS_Uri *uri; | 77 | struct GNUNET_FS_Uri *uri; |
78 | 78 | ||
79 | /** | 79 | /** |
80 | * Overall file size. | 80 | * Overall file size. |
81 | */ | 81 | */ |
@@ -87,12 +87,12 @@ struct GNUNET_FS_TreeEncoder | |||
87 | uint64_t publish_offset; | 87 | uint64_t publish_offset; |
88 | 88 | ||
89 | /** | 89 | /** |
90 | * How deep are we? | 90 | * How deep are we? Depth 0 is for the DBLOCKs. |
91 | */ | 91 | */ |
92 | unsigned int current_depth; | 92 | unsigned int current_depth; |
93 | 93 | ||
94 | /** | 94 | /** |
95 | * How deep is the tree? | 95 | * How deep is the tree? Always > 0. |
96 | */ | 96 | */ |
97 | unsigned int chk_tree_depth; | 97 | unsigned int chk_tree_depth; |
98 | 98 | ||
@@ -121,7 +121,7 @@ struct GNUNET_FS_TreeEncoder | |||
121 | * Compute the depth of the CHK tree. | 121 | * Compute the depth of the CHK tree. |
122 | * | 122 | * |
123 | * @param flen file length for which to compute the depth | 123 | * @param flen file length for which to compute the depth |
124 | * @return depth of the tree | 124 | * @return depth of the tree, always > 0. A depth of 1 means only a DBLOCK. |
125 | */ | 125 | */ |
126 | unsigned int | 126 | unsigned int |
127 | GNUNET_FS_compute_depth (uint64_t flen) | 127 | GNUNET_FS_compute_depth (uint64_t flen) |
@@ -146,73 +146,53 @@ GNUNET_FS_compute_depth (uint64_t flen) | |||
146 | 146 | ||
147 | 147 | ||
148 | /** | 148 | /** |
149 | * Initialize a tree encoder. This function will call "proc" and | 149 | * Calculate how many bytes of payload a block tree of the given |
150 | * "progress" on each block in the tree. Once all blocks have been | 150 | * depth MAY correspond to at most (this function ignores the fact that |
151 | * processed, "cont" will be scheduled. The "reader" will be called | 151 | * some blocks will only be present partially due to the total file |
152 | * to obtain the (plaintext) blocks for the file. Note that this | 152 | * size cutting some blocks off at the end). |
153 | * function will not actually call "proc". The client must | ||
154 | * call "GNUNET_FS_tree_encoder_next" to trigger encryption (and | ||
155 | * calling of "proc") for the each block. | ||
156 | * | 153 | * |
157 | * @param h the global FS context | 154 | * @param depth depth of the block. depth==0 is a DBLOCK. |
158 | * @param size overall size of the file to encode | 155 | * @return number of bytes of payload a subtree of this depth may correspond to |
159 | * @param cls closure for reader, proc, progress and cont | ||
160 | * @param reader function to call to read plaintext data | ||
161 | * @param proc function to call on each encrypted block | ||
162 | * @param progress function to call with progress information | ||
163 | * @param cont function to call when done | ||
164 | */ | 156 | */ |
165 | struct GNUNET_FS_TreeEncoder * | 157 | uint64_t |
166 | GNUNET_FS_tree_encoder_create (struct GNUNET_FS_Handle *h, | 158 | GNUNET_FS_tree_compute_tree_size (unsigned int depth) |
167 | uint64_t size, | ||
168 | void *cls, | ||
169 | GNUNET_FS_DataReader reader, | ||
170 | GNUNET_FS_TreeBlockProcessor proc, | ||
171 | GNUNET_FS_TreeProgressCallback progress, | ||
172 | GNUNET_SCHEDULER_Task cont) | ||
173 | { | 159 | { |
174 | struct GNUNET_FS_TreeEncoder *te; | 160 | uint64_t rsize; |
175 | 161 | unsigned int i; | |
176 | te = GNUNET_malloc (sizeof (struct GNUNET_FS_TreeEncoder)); | 162 | |
177 | te->h = h; | 163 | rsize = DBLOCK_SIZE; |
178 | te->size = size; | 164 | for (i = 0; i<depth; i++) |
179 | te->cls = cls; | 165 | rsize *= CHK_PER_INODE; |
180 | te->reader = reader; | 166 | return rsize; |
181 | te->proc = proc; | ||
182 | te->progress = progress; | ||
183 | te->cont = cont; | ||
184 | te->chk_tree_depth = GNUNET_FS_compute_depth (size); | ||
185 | te->current_depth = te->chk_tree_depth; | ||
186 | te->chk_tree = GNUNET_malloc (te->chk_tree_depth * | ||
187 | CHK_PER_INODE * | ||
188 | sizeof (struct ContentHashKey)); | ||
189 | return te; | ||
190 | } | 167 | } |
191 | 168 | ||
192 | 169 | ||
193 | /** | 170 | /** |
194 | * Compute the size of the current IBlock. | 171 | * Compute the size of the current IBLOCK. The encoder is |
172 | * triggering the calculation of the size of an IBLOCK at the | ||
173 | * *end* (hence end_offset) of its construction. The IBLOCK | ||
174 | * maybe a full or a partial IBLOCK, and this function is to | ||
175 | * calculate how long it should be. | ||
195 | * | 176 | * |
196 | * @param height height of the IBlock in the tree (aka overall | 177 | * @param depth depth of the IBlock in the tree, 0 would be a DBLOCK, |
197 | * number of tree levels minus depth); 0 == DBlock | 178 | * must be > 0 (this function is for IBLOCKs only!) |
198 | * @param offset current offset in the overall file | 179 | * @param end_offset current offset in the payload (!) of the overall file, |
180 | * must be > 0 (since this function is called at the | ||
181 | * end of a block). | ||
199 | * @return size of the corresponding IBlock | 182 | * @return size of the corresponding IBlock |
200 | */ | 183 | */ |
201 | uint16_t | 184 | static uint16_t |
202 | GNUNET_FS_tree_compute_iblock_size (unsigned int height, | 185 | GNUNET_FS_tree_compute_iblock_size (unsigned int depth, |
203 | uint64_t offset) | 186 | uint64_t end_offset) |
204 | { | 187 | { |
205 | unsigned int ret; | 188 | unsigned int ret; |
206 | unsigned int i; | ||
207 | uint64_t mod; | 189 | uint64_t mod; |
208 | uint64_t bds; | 190 | uint64_t bds; |
209 | 191 | ||
210 | GNUNET_assert (height > 0); | 192 | GNUNET_assert (depth > 0); |
211 | bds = DBLOCK_SIZE; /* number of bytes each CHK at level "i" | 193 | GNUNET_assert (end_offset > 0); |
212 | corresponds to */ | 194 | bds = GNUNET_FS_tree_compute_tree_size (depth); |
213 | for (i=0;i<height;i++) | 195 | mod = end_offset % bds; |
214 | bds *= CHK_PER_INODE; | ||
215 | mod = offset % bds; | ||
216 | if (0 == mod) | 196 | if (0 == mod) |
217 | { | 197 | { |
218 | /* we were triggered at the end of a full block */ | 198 | /* we were triggered at the end of a full block */ |
@@ -232,42 +212,40 @@ GNUNET_FS_tree_compute_iblock_size (unsigned int height, | |||
232 | 212 | ||
233 | /** | 213 | /** |
234 | * Compute how many bytes of data should be stored in | 214 | * Compute how many bytes of data should be stored in |
235 | * the specified node. | 215 | * the specified block. |
236 | * | 216 | * |
237 | * @param fsize overall file size | 217 | * @param fsize overall file size, must be > 0. |
238 | * @param totaldepth depth of the entire tree | 218 | * @param offset offset in the original data corresponding |
239 | * @param offset offset of the node | 219 | * to the beginning of the tree induced by the block; |
240 | * @param depth depth of the node | 220 | * must be <= fsize |
221 | * @param depth depth of the node in the tree, 0 for DBLOCK | ||
241 | * @return number of bytes stored in this node | 222 | * @return number of bytes stored in this node |
242 | */ | 223 | */ |
243 | size_t | 224 | size_t |
244 | GNUNET_FS_tree_calculate_block_size (uint64_t fsize, | 225 | GNUNET_FS_tree_calculate_block_size (uint64_t fsize, |
245 | unsigned int totaldepth, | ||
246 | uint64_t offset, | 226 | uint64_t offset, |
247 | unsigned int depth) | 227 | unsigned int depth) |
248 | { | 228 | { |
249 | unsigned int i; | ||
250 | size_t ret; | 229 | size_t ret; |
251 | uint64_t rsize; | 230 | uint64_t rsize; |
252 | uint64_t epos; | 231 | uint64_t epos; |
253 | unsigned int chks; | 232 | unsigned int chks; |
254 | 233 | ||
234 | GNUNET_assert (fsize > 0); | ||
255 | GNUNET_assert (offset <= fsize); | 235 | GNUNET_assert (offset <= fsize); |
256 | if (depth == totaldepth) | 236 | if (depth == 0) |
257 | { | 237 | { |
258 | ret = DBLOCK_SIZE; | 238 | ret = DBLOCK_SIZE; |
259 | if (offset + ret > fsize) | 239 | if ( (offset + ret > fsize) || |
240 | (offset + ret < offset) ) | ||
260 | ret = (size_t) (fsize - offset); | 241 | ret = (size_t) (fsize - offset); |
261 | return ret; | 242 | return ret; |
262 | } | 243 | } |
263 | /* FIXME: this code should be *equivalent* to the | 244 | |
264 | GNUNET_FS_tree_compute_iblock_size function above! Remove duplication! */ | 245 | rsize = GNUNET_FS_tree_compute_tree_size (depth - 1); |
265 | rsize = DBLOCK_SIZE; | ||
266 | for (i = totaldepth-1; i > depth; i--) | ||
267 | rsize *= CHK_PER_INODE; | ||
268 | epos = offset + rsize * CHK_PER_INODE; | 246 | epos = offset + rsize * CHK_PER_INODE; |
269 | GNUNET_assert (epos > offset); | 247 | if ( (epos < offset) || |
270 | if (epos > fsize) | 248 | (epos > fsize) ) |
271 | epos = fsize; | 249 | epos = fsize; |
272 | /* round up when computing #CHKs in our IBlock */ | 250 | /* round up when computing #CHKs in our IBlock */ |
273 | chks = (epos - offset + rsize - 1) / rsize; | 251 | chks = (epos - offset + rsize - 1) / rsize; |
@@ -277,29 +255,71 @@ GNUNET_FS_tree_calculate_block_size (uint64_t fsize, | |||
277 | 255 | ||
278 | 256 | ||
279 | /** | 257 | /** |
258 | * Initialize a tree encoder. This function will call "proc" and | ||
259 | * "progress" on each block in the tree. Once all blocks have been | ||
260 | * processed, "cont" will be scheduled. The "reader" will be called | ||
261 | * to obtain the (plaintext) blocks for the file. Note that this | ||
262 | * function will not actually call "proc". The client must | ||
263 | * call "GNUNET_FS_tree_encoder_next" to trigger encryption (and | ||
264 | * calling of "proc") for the each block. | ||
265 | * | ||
266 | * @param h the global FS context | ||
267 | * @param size overall size of the file to encode | ||
268 | * @param cls closure for reader, proc, progress and cont | ||
269 | * @param reader function to call to read plaintext data | ||
270 | * @param proc function to call on each encrypted block | ||
271 | * @param progress function to call with progress information | ||
272 | * @param cont function to call when done | ||
273 | */ | ||
274 | struct GNUNET_FS_TreeEncoder * | ||
275 | GNUNET_FS_tree_encoder_create (struct GNUNET_FS_Handle *h, | ||
276 | uint64_t size, | ||
277 | void *cls, | ||
278 | GNUNET_FS_DataReader reader, | ||
279 | GNUNET_FS_TreeBlockProcessor proc, | ||
280 | GNUNET_FS_TreeProgressCallback progress, | ||
281 | GNUNET_SCHEDULER_Task cont) | ||
282 | { | ||
283 | struct GNUNET_FS_TreeEncoder *te; | ||
284 | |||
285 | te = GNUNET_malloc (sizeof (struct GNUNET_FS_TreeEncoder)); | ||
286 | te->h = h; | ||
287 | te->size = size; | ||
288 | te->cls = cls; | ||
289 | te->reader = reader; | ||
290 | te->proc = proc; | ||
291 | te->progress = progress; | ||
292 | te->cont = cont; | ||
293 | te->chk_tree_depth = GNUNET_FS_compute_depth (size); | ||
294 | te->chk_tree = GNUNET_malloc (te->chk_tree_depth * | ||
295 | CHK_PER_INODE * | ||
296 | sizeof (struct ContentHashKey)); | ||
297 | return te; | ||
298 | } | ||
299 | |||
300 | |||
301 | /** | ||
280 | * Compute the offset of the CHK for the | 302 | * Compute the offset of the CHK for the |
281 | * current block in the IBlock above. | 303 | * current block in the IBlock above. |
282 | * | 304 | * |
283 | * @param height height of the IBlock in the tree (aka overall | 305 | * @param depth depth of the IBlock in the tree (aka overall |
284 | * number of tree levels minus depth); 0 == DBlock | 306 | * number of tree levels minus depth); 0 == DBlock |
285 | * @param offset current offset in the overall file | 307 | * @param end_offset current offset in the overall file, |
308 | * at the *beginning* of the block for DBLOCKs (depth==0), | ||
309 | * otherwise at the *end* of the block (exclusive) | ||
286 | * @return (array of CHKs') offset in the above IBlock | 310 | * @return (array of CHKs') offset in the above IBlock |
287 | */ | 311 | */ |
288 | static unsigned int | 312 | static unsigned int |
289 | compute_chk_offset (unsigned int height, | 313 | compute_chk_offset (unsigned int depth, |
290 | uint64_t offset) | 314 | uint64_t end_offset) |
291 | { | 315 | { |
292 | uint64_t bds; | 316 | uint64_t bds; |
293 | unsigned int ret; | 317 | unsigned int ret; |
294 | unsigned int i; | ||
295 | 318 | ||
296 | bds = DBLOCK_SIZE; /* number of bytes each CHK at level "i" | 319 | bds = GNUNET_FS_tree_compute_tree_size (depth); |
297 | corresponds to */ | 320 | if (depth > 0) |
298 | for (i=0;i<height;i++) | 321 | end_offset--; /* round down since for depth > 0 offset is at the END of the block */ |
299 | bds *= CHK_PER_INODE; | 322 | ret = end_offset / bds; |
300 | if (height > 0) | ||
301 | offset--; /* round down since for height > 0 offset is at the END of the block */ | ||
302 | ret = offset / bds; | ||
303 | return ret % CHK_PER_INODE; | 323 | return ret % CHK_PER_INODE; |
304 | } | 324 | } |
305 | 325 | ||
@@ -325,8 +345,26 @@ GNUNET_FS_tree_encoder_next (struct GNUNET_FS_TreeEncoder * te) | |||
325 | 345 | ||
326 | GNUNET_assert (GNUNET_NO == te->in_next); | 346 | GNUNET_assert (GNUNET_NO == te->in_next); |
327 | te->in_next = GNUNET_YES; | 347 | te->in_next = GNUNET_YES; |
328 | if (te->current_depth == te->chk_tree_depth) | 348 | if (te->chk_tree_depth == te->current_depth) |
349 | { | ||
350 | off = CHK_PER_INODE * (te->chk_tree_depth - 1); | ||
351 | #if DEBUG_TREE | ||
352 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
353 | "TE done, reading CHK `%s' from %u\n", | ||
354 | GNUNET_h2s (&te->chk_tree[off].query), | ||
355 | off); | ||
356 | #endif | ||
357 | te->uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri)); | ||
358 | te->uri->type = chk; | ||
359 | te->uri->data.chk.chk = te->chk_tree[off]; | ||
360 | te->uri->data.chk.file_length = GNUNET_htonll (te->size); | ||
361 | te->in_next = GNUNET_NO; | ||
362 | te->cont (te->cls, NULL); | ||
363 | return; | ||
364 | } | ||
365 | if (0 == te->current_depth) | ||
329 | { | 366 | { |
367 | /* read DBLOCK */ | ||
330 | pt_size = GNUNET_MIN(DBLOCK_SIZE, | 368 | pt_size = GNUNET_MIN(DBLOCK_SIZE, |
331 | te->size - te->publish_offset); | 369 | te->size - te->publish_offset); |
332 | if (pt_size != | 370 | if (pt_size != |
@@ -346,24 +384,12 @@ GNUNET_FS_tree_encoder_next (struct GNUNET_FS_TreeEncoder * te) | |||
346 | } | 384 | } |
347 | else | 385 | else |
348 | { | 386 | { |
349 | pt_size = GNUNET_FS_tree_compute_iblock_size (te->chk_tree_depth - te->current_depth, | 387 | pt_size = GNUNET_FS_tree_compute_iblock_size (te->current_depth, |
350 | te->publish_offset); | 388 | te->publish_offset); |
351 | pt_block = &te->chk_tree[te->current_depth * | 389 | pt_block = &te->chk_tree[(te->current_depth - 1) * |
352 | CHK_PER_INODE]; | 390 | CHK_PER_INODE]; |
353 | } | 391 | } |
354 | if (0 == te->current_depth) | 392 | off = compute_chk_offset (te->current_depth, |
355 | { | ||
356 | te->uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri)); | ||
357 | te->uri->type = chk; | ||
358 | te->uri->data.chk.chk = te->chk_tree[0]; | ||
359 | te->uri->data.chk.file_length = GNUNET_htonll (te->size); | ||
360 | GNUNET_SCHEDULER_add_continuation (te->cont, | ||
361 | te->cls, | ||
362 | GNUNET_SCHEDULER_REASON_PREREQ_DONE); | ||
363 | te->in_next = GNUNET_NO; | ||
364 | return; | ||
365 | } | ||
366 | off = compute_chk_offset (te->chk_tree_depth - te->current_depth, | ||
367 | te->publish_offset); | 393 | te->publish_offset); |
368 | #if DEBUG_TREE | 394 | #if DEBUG_TREE |
369 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | 395 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, |
@@ -373,7 +399,7 @@ GNUNET_FS_tree_encoder_next (struct GNUNET_FS_TreeEncoder * te) | |||
373 | (unsigned int) pt_size, | 399 | (unsigned int) pt_size, |
374 | (unsigned int) off); | 400 | (unsigned int) off); |
375 | #endif | 401 | #endif |
376 | mychk = &te->chk_tree[(te->current_depth-1)*CHK_PER_INODE+off]; | 402 | mychk = &te->chk_tree[te->current_depth*CHK_PER_INODE+off]; |
377 | GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key); | 403 | GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key); |
378 | GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv); | 404 | GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv); |
379 | GNUNET_CRYPTO_aes_encrypt (pt_block, | 405 | GNUNET_CRYPTO_aes_encrypt (pt_block, |
@@ -384,15 +410,16 @@ GNUNET_FS_tree_encoder_next (struct GNUNET_FS_TreeEncoder * te) | |||
384 | GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query); | 410 | GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query); |
385 | #if DEBUG_TREE | 411 | #if DEBUG_TREE |
386 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | 412 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, |
387 | "TE calculates query to be `%s'\n", | 413 | "TE calculates query to be `%s', stored at %u\n", |
388 | GNUNET_h2s (&mychk->query)); | 414 | GNUNET_h2s (&mychk->query), |
415 | te->current_depth * CHK_PER_INODE + off); | ||
389 | #endif | 416 | #endif |
390 | if (NULL != te->proc) | 417 | if (NULL != te->proc) |
391 | te->proc (te->cls, | 418 | te->proc (te->cls, |
392 | &mychk->query, | 419 | mychk, |
393 | te->publish_offset, | 420 | te->publish_offset, |
394 | te->current_depth, | 421 | te->current_depth, |
395 | (te->current_depth == te->chk_tree_depth) | 422 | (0 == te->current_depth) |
396 | ? GNUNET_BLOCK_TYPE_FS_DBLOCK | 423 | ? GNUNET_BLOCK_TYPE_FS_DBLOCK |
397 | : GNUNET_BLOCK_TYPE_FS_IBLOCK, | 424 | : GNUNET_BLOCK_TYPE_FS_IBLOCK, |
398 | enc, | 425 | enc, |
@@ -403,20 +430,20 @@ GNUNET_FS_tree_encoder_next (struct GNUNET_FS_TreeEncoder * te) | |||
403 | pt_block, | 430 | pt_block, |
404 | pt_size, | 431 | pt_size, |
405 | te->current_depth); | 432 | te->current_depth); |
406 | if (te->current_depth == te->chk_tree_depth) | 433 | if (0 == te->current_depth) |
407 | { | 434 | { |
408 | te->publish_offset += pt_size; | 435 | te->publish_offset += pt_size; |
409 | if ( (te->publish_offset == te->size) || | 436 | if ( (te->publish_offset == te->size) || |
410 | (0 == te->publish_offset % (CHK_PER_INODE * DBLOCK_SIZE) ) ) | 437 | (0 == te->publish_offset % (CHK_PER_INODE * DBLOCK_SIZE) ) ) |
411 | te->current_depth--; | 438 | te->current_depth++; |
412 | } | 439 | } |
413 | else | 440 | else |
414 | { | 441 | { |
415 | if ( (off == CHK_PER_INODE) || | 442 | if ( (off == CHK_PER_INODE) || |
416 | (te->publish_offset == te->size) ) | 443 | (te->publish_offset == te->size) ) |
417 | te->current_depth--; | 444 | te->current_depth++; |
418 | else | 445 | else |
419 | te->current_depth = te->chk_tree_depth; | 446 | te->current_depth = 0; |
420 | } | 447 | } |
421 | te->in_next = GNUNET_NO; | 448 | te->in_next = GNUNET_NO; |
422 | } | 449 | } |
@@ -433,10 +460,11 @@ GNUNET_FS_tree_encoder_next (struct GNUNET_FS_TreeEncoder * te) | |||
433 | * prior to completion and prior to an internal error, | 460 | * prior to completion and prior to an internal error, |
434 | * both "*uri" and "*emsg" will be set to NULL). | 461 | * both "*uri" and "*emsg" will be set to NULL). |
435 | */ | 462 | */ |
436 | void GNUNET_FS_tree_encoder_finish (struct GNUNET_FS_TreeEncoder * te, | 463 | void GNUNET_FS_tree_encoder_finish (struct GNUNET_FS_TreeEncoder *te, |
437 | struct GNUNET_FS_Uri **uri, | 464 | struct GNUNET_FS_Uri **uri, |
438 | char **emsg) | 465 | char **emsg) |
439 | { | 466 | { |
467 | GNUNET_assert (GNUNET_NO == te->in_next); | ||
440 | if (uri != NULL) | 468 | if (uri != NULL) |
441 | *uri = te->uri; | 469 | *uri = te->uri; |
442 | else | 470 | else |