source: mainline/uspace/srv/net/inetsrv/pdu.c@ fc6abbe

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since fc6abbe was fc6abbe, checked in by Jiri Svoboda <jiri@…>, 11 years ago

Fix a few bugs in IP reassembly code (thx Fan Jinfei).

  • Property mode set to 100644
File size: 15.3 KB
RevLine 
[ceba4bed]1/*
2 * Copyright (c) 2012 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup inet
30 * @{
31 */
32/**
33 * @file
34 * @brief
35 */
36
[e767dbf]37#include <align.h>
38#include <bitops.h>
39#include <byteorder.h>
[ceba4bed]40#include <errno.h>
[1cc8b42]41#include <fibril_synch.h>
[e767dbf]42#include <io/log.h>
[347768d]43#include <macros.h>
[e767dbf]44#include <mem.h>
[ceba4bed]45#include <stdlib.h>
[b4ec1ea]46#include "inetsrv.h"
[e767dbf]47#include "inet_std.h"
[ceba4bed]48#include "pdu.h"
49
[1cc8b42]50/** One's complement addition.
51 *
52 * Result is a + b + carry.
53 */
54static uint16_t inet_ocadd16(uint16_t a, uint16_t b)
55{
56 uint32_t s;
57
58 s = (uint32_t)a + (uint32_t)b;
59 return (s & 0xffff) + (s >> 16);
60}
61
[637a3b4]62uint16_t inet_checksum_calc(uint16_t ivalue, void *data, size_t size)
[1cc8b42]63{
64 uint16_t sum;
65 uint16_t w;
66 size_t words, i;
67 uint8_t *bdata;
68
69 sum = ~ivalue;
70 words = size / 2;
71 bdata = (uint8_t *)data;
72
73 for (i = 0; i < words; i++) {
74 w = ((uint16_t)bdata[2*i] << 8) | bdata[2*i + 1];
75 sum = inet_ocadd16(sum, w);
76 }
77
78 if (size % 2 != 0) {
79 w = ((uint16_t)bdata[2*words] << 8);
80 sum = inet_ocadd16(sum, w);
81 }
82
83 return ~sum;
84}
85
[a17356fd]86/** Encode IPv4 PDU.
[347768d]87 *
88 * Encode internet packet into PDU (serialized form). Will encode a
89 * fragment of the payload starting at offset @a offs. The resulting
90 * PDU will have at most @a mtu bytes. @a *roffs will be set to the offset
91 * of remaining payload. If some data is remaining, the MF flag will
92 * be set in the header, otherwise the offset will equal @a packet->size.
93 *
[a17356fd]94 * @param packet Packet to encode
95 * @param src Source address
96 * @param dest Destination address
97 * @param offs Offset into packet payload (in bytes)
98 * @param mtu MTU (Maximum Transmission Unit) in bytes
99 * @param rdata Place to store pointer to allocated data buffer
100 * @param rsize Place to store size of allocated data buffer
101 * @param roffs Place to store offset of remaning data
102 *
[ceba4bed]103 */
[a17356fd]104int inet_pdu_encode(inet_packet_t *packet, addr32_t src, addr32_t dest,
[313824a]105 size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
[ceba4bed]106{
[a17356fd]107 /* Upper bound for fragment offset field */
[fc6abbe]108 size_t fragoff_limit = 1 << (FF_FRAGOFF_h - FF_FRAGOFF_l + 1);
[a17356fd]109
110 /* Verify that total size of datagram is within reasonable bounds */
[fc6abbe]111 if (packet->size > FRAG_OFFS_UNIT * fragoff_limit)
[a17356fd]112 return ELIMIT;
[02a09ed]113
[a17356fd]114 size_t hdr_size = sizeof(ip_header_t);
[44c9ef4]115 if (hdr_size >= mtu)
116 return EINVAL;
[a2e3ee6]117
[44c9ef4]118 assert(hdr_size % 4 == 0);
[a17356fd]119 assert(offs % FRAG_OFFS_UNIT == 0);
120 assert(offs / FRAG_OFFS_UNIT < fragoff_limit);
121
122 /* Value for the fragment offset field */
123 uint16_t foff = offs / FRAG_OFFS_UNIT;
124
125 /* Amount of space in the PDU available for payload */
126 size_t spc_avail = mtu - hdr_size;
127 spc_avail -= (spc_avail % FRAG_OFFS_UNIT);
128
129 /* Amount of data (payload) to transfer */
130 size_t xfer_size = min(packet->size - offs, spc_avail);
131
132 /* Total PDU size */
133 size_t size = hdr_size + xfer_size;
134
135 /* Offset of remaining payload */
136 size_t rem_offs = offs + xfer_size;
137
138 /* Flags */
139 uint16_t flags_foff =
140 (packet->df ? BIT_V(uint16_t, FF_FLAG_DF) : 0) +
141 (rem_offs < packet->size ? BIT_V(uint16_t, FF_FLAG_MF) : 0) +
142 (foff << FF_FRAGOFF_l);
143
144 void *data = calloc(size, 1);
145 if (data == NULL)
146 return ENOMEM;
147
148 /* Encode header fields */
149 ip_header_t *hdr = (ip_header_t *) data;
150
151 hdr->ver_ihl =
152 (4 << VI_VERSION_l) | (hdr_size / sizeof(uint32_t));
153 hdr->tos = packet->tos;
154 hdr->tot_len = host2uint16_t_be(size);
[313824a]155 hdr->id = host2uint16_t_be(packet->ident);
[a17356fd]156 hdr->flags_foff = host2uint16_t_be(flags_foff);
157 hdr->ttl = packet->ttl;
158 hdr->proto = packet->proto;
159 hdr->chksum = 0;
160 hdr->src_addr = host2uint32_t_be(src);
161 hdr->dest_addr = host2uint32_t_be(dest);
162
163 /* Compute checksum */
164 uint16_t chksum = inet_checksum_calc(INET_CHECKSUM_INIT,
165 (void *) hdr, hdr_size);
166 hdr->chksum = host2uint16_t_be(chksum);
167
168 /* Copy payload */
[44c9ef4]169 memcpy((uint8_t *) data + hdr_size, packet->data + offs, xfer_size);
[a17356fd]170
171 *rdata = data;
172 *rsize = size;
173 *roffs = rem_offs;
174
175 return EOK;
176}
177
178/** Encode IPv6 PDU.
179 *
180 * Encode internet packet into PDU (serialized form). Will encode a
181 * fragment of the payload starting at offset @a offs. The resulting
182 * PDU will have at most @a mtu bytes. @a *roffs will be set to the offset
183 * of remaining payload. If some data is remaining, the MF flag will
184 * be set in the header, otherwise the offset will equal @a packet->size.
185 *
186 * @param packet Packet to encode
187 * @param src Source address
188 * @param dest Destination address
189 * @param offs Offset into packet payload (in bytes)
190 * @param mtu MTU (Maximum Transmission Unit) in bytes
191 * @param rdata Place to store pointer to allocated data buffer
192 * @param rsize Place to store size of allocated data buffer
[44c9ef4]193 * @param roffs Place to store offset of remaning data
[a17356fd]194 *
195 */
196int inet_pdu_encode6(inet_packet_t *packet, addr128_t src, addr128_t dest,
197 size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
198{
[44c9ef4]199 /* IPv6 mandates a minimal MTU of 1280 bytes */
200 if (mtu < 1280)
201 return ELIMIT;
202
[347768d]203 /* Upper bound for fragment offset field */
[44c9ef4]204 size_t fragoff_limit = 1 << (OF_FRAGOFF_h - OF_FRAGOFF_l);
[a2e3ee6]205
[347768d]206 /* Verify that total size of datagram is within reasonable bounds */
207 if (offs + packet->size > FRAG_OFFS_UNIT * fragoff_limit)
208 return ELIMIT;
[a2e3ee6]209
[44c9ef4]210 /* Determine whether we need the Fragment extension header */
211 bool fragment;
212 if (offs == 0)
213 fragment = (packet->size + sizeof(ip6_header_t) > mtu);
214 else
215 fragment = true;
216
217 size_t hdr_size;
218 if (fragment)
219 hdr_size = sizeof(ip6_header_t) + sizeof(ip6_header_fragment_t);
220 else
221 hdr_size = sizeof(ip6_header_t);
[02a09ed]222
[44c9ef4]223 if (hdr_size >= mtu)
224 return EINVAL;
[a2e3ee6]225
[44c9ef4]226 assert(sizeof(ip6_header_t) % 8 == 0);
227 assert(hdr_size % 8 == 0);
[347768d]228 assert(offs % FRAG_OFFS_UNIT == 0);
229 assert(offs / FRAG_OFFS_UNIT < fragoff_limit);
[a2e3ee6]230
[347768d]231 /* Value for the fragment offset field */
[a2e3ee6]232 uint16_t foff = offs / FRAG_OFFS_UNIT;
233
[347768d]234 /* Amount of space in the PDU available for payload */
[a2e3ee6]235 size_t spc_avail = mtu - hdr_size;
[7fda2e0]236 spc_avail -= (spc_avail % FRAG_OFFS_UNIT);
[a2e3ee6]237
[347768d]238 /* Amount of data (payload) to transfer */
[a2e3ee6]239 size_t xfer_size = min(packet->size - offs, spc_avail);
240
[347768d]241 /* Total PDU size */
[a2e3ee6]242 size_t size = hdr_size + xfer_size;
243
[347768d]244 /* Offset of remaining payload */
[a2e3ee6]245 size_t rem_offs = offs + xfer_size;
246
[347768d]247 /* Flags */
[44c9ef4]248 uint16_t offsmf =
249 (rem_offs < packet->size ? BIT_V(uint16_t, OF_FLAG_M) : 0) +
250 (foff << OF_FRAGOFF_l);
[a2e3ee6]251
252 void *data = calloc(size, 1);
[ceba4bed]253 if (data == NULL)
254 return ENOMEM;
[a2e3ee6]255
[347768d]256 /* Encode header fields */
[a17356fd]257 ip6_header_t *hdr6 = (ip6_header_t *) data;
258
259 hdr6->ver_tc = (6 << (VI_VERSION_l));
260 memset(hdr6->tc_fl, 0, 3);
261 hdr6->hop_limit = packet->ttl;
262
263 host2addr128_t_be(src, hdr6->src_addr);
264 host2addr128_t_be(dest, hdr6->dest_addr);
[a2e3ee6]265
[44c9ef4]266 /* Optionally encode Fragment extension header fields */
267 if (fragment) {
268 assert(offsmf != 0);
269
270 hdr6->payload_len = host2uint16_t_be(packet->size +
271 sizeof(ip6_header_fragment_t));
272 hdr6->next = IP6_NEXT_FRAGMENT;
273
274 ip6_header_fragment_t *hdr6f = (ip6_header_fragment_t *)
275 (hdr6 + 1);
276
277 hdr6f->next = packet->proto;
278 hdr6f->reserved = 0;
279 hdr6f->offsmf = host2uint16_t_be(offsmf);
280 hdr6f->id = host2uint32_t_be(packet->ident);
281 } else {
282 assert(offsmf == 0);
283
284 hdr6->payload_len = host2uint16_t_be(packet->size);
285 hdr6->next = packet->proto;
286 }
287
[347768d]288 /* Copy payload */
[44c9ef4]289 memcpy((uint8_t *) data + hdr_size, packet->data + offs, xfer_size);
[a2e3ee6]290
[ceba4bed]291 *rdata = data;
292 *rsize = size;
[347768d]293 *roffs = rem_offs;
[a2e3ee6]294
[ceba4bed]295 return EOK;
296}
297
[1d94e21]298/** Decode IPv4 datagram
299 *
300 * @param data Serialized IPv4 datagram
301 * @param size Length of serialized IPv4 datagram
302 * @param packet IP datagram structure to be filled
303 *
304 * @return EOK on success
305 * @return EINVAL if the datagram is invalid or damaged
306 * @return ENOMEM if not enough memory
307 *
308 */
[fe4310f]309int inet_pdu_decode(void *data, size_t size, inet_packet_t *packet)
[e767dbf]310{
[a1a101d]311 log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_pdu_decode()");
[257feec]312
[e767dbf]313 if (size < sizeof(ip_header_t)) {
[a1a101d]314 log_msg(LOG_DEFAULT, LVL_DEBUG, "PDU too short (%zu)", size);
[e767dbf]315 return EINVAL;
316 }
[02a09ed]317
318 ip_header_t *hdr = (ip_header_t *) data;
319
320 uint8_t version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h,
321 VI_VERSION_l, hdr->ver_ihl);
[e767dbf]322 if (version != 4) {
[a1a101d]323 log_msg(LOG_DEFAULT, LVL_DEBUG, "Version (%d) != 4", version);
[e767dbf]324 return EINVAL;
325 }
[02a09ed]326
327 size_t tot_len = uint16_t_be2host(hdr->tot_len);
[e767dbf]328 if (tot_len < sizeof(ip_header_t)) {
[a1a101d]329 log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length too small (%zu)", tot_len);
[e767dbf]330 return EINVAL;
331 }
[257feec]332
[e767dbf]333 if (tot_len > size) {
[a1a101d]334 log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length = %zu > PDU size = %zu",
[a17356fd]335 tot_len, size);
[e767dbf]336 return EINVAL;
337 }
[02a09ed]338
339 uint16_t ident = uint16_t_be2host(hdr->id);
340 uint16_t flags_foff = uint16_t_be2host(hdr->flags_foff);
341 uint16_t foff = BIT_RANGE_EXTRACT(uint16_t, FF_FRAGOFF_h, FF_FRAGOFF_l,
[7f95c904]342 flags_foff);
[e767dbf]343 /* XXX Checksum */
[02a09ed]344
345 inet_addr_set(uint32_t_be2host(hdr->src_addr), &packet->src);
346 inet_addr_set(uint32_t_be2host(hdr->dest_addr), &packet->dest);
[fe4310f]347 packet->tos = hdr->tos;
[2ff150e]348 packet->proto = hdr->proto;
[fe4310f]349 packet->ttl = hdr->ttl;
[7f95c904]350 packet->ident = ident;
[257feec]351
[7f95c904]352 packet->df = (flags_foff & BIT_V(uint16_t, FF_FLAG_DF)) != 0;
353 packet->mf = (flags_foff & BIT_V(uint16_t, FF_FLAG_MF)) != 0;
354 packet->offs = foff * FRAG_OFFS_UNIT;
[257feec]355
[e767dbf]356 /* XXX IP options */
[02a09ed]357 size_t data_offs = sizeof(uint32_t) *
358 BIT_RANGE_EXTRACT(uint8_t, VI_IHL_h, VI_IHL_l, hdr->ver_ihl);
359
[fe4310f]360 packet->size = tot_len - data_offs;
361 packet->data = calloc(packet->size, 1);
362 if (packet->data == NULL) {
[a1a101d]363 log_msg(LOG_DEFAULT, LVL_WARN, "Out of memory.");
[e767dbf]364 return ENOMEM;
365 }
[257feec]366
367 memcpy(packet->data, (uint8_t *) data + data_offs, packet->size);
368
[e767dbf]369 return EOK;
370}
371
[1d94e21]372/** Decode IPv6 datagram
373 *
374 * @param data Serialized IPv6 datagram
375 * @param size Length of serialized IPv6 datagram
376 * @param packet IP datagram structure to be filled
377 *
378 * @return EOK on success
379 * @return EINVAL if the datagram is invalid or damaged
380 * @return ENOMEM if not enough memory
381 *
382 */
[1d24ad3]383int inet_pdu_decode6(void *data, size_t size, inet_packet_t *packet)
384{
[a17356fd]385 log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_pdu_decode6()");
386
387 if (size < sizeof(ip6_header_t)) {
388 log_msg(LOG_DEFAULT, LVL_DEBUG, "PDU too short (%zu)", size);
389 return EINVAL;
390 }
391
392 ip6_header_t *hdr6 = (ip6_header_t *) data;
393
394 uint8_t version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h,
395 VI_VERSION_l, hdr6->ver_tc);
396 if (version != 6) {
397 log_msg(LOG_DEFAULT, LVL_DEBUG, "Version (%d) != 6", version);
398 return EINVAL;
399 }
400
401 size_t payload_len = uint16_t_be2host(hdr6->payload_len);
402 if (payload_len + sizeof(ip6_header_t) > size) {
[44c9ef4]403 log_msg(LOG_DEFAULT, LVL_DEBUG, "Payload Length = %zu > PDU size = %zu",
[a17356fd]404 payload_len + sizeof(ip6_header_t), size);
405 return EINVAL;
406 }
407
[44c9ef4]408 uint32_t ident;
409 uint16_t offsmf;
410 uint16_t foff;
411 uint16_t next;
412 size_t data_offs = sizeof(ip6_header_t);
413
414 /* Fragment extension header */
415 if (hdr6->next == IP6_NEXT_FRAGMENT) {
416 ip6_header_fragment_t *hdr6f = (ip6_header_fragment_t *)
417 (hdr6 + 1);
418
419 ident = uint32_t_be2host(hdr6f->id);
420 offsmf = uint16_t_be2host(hdr6f->offsmf);
421 foff = BIT_RANGE_EXTRACT(uint16_t, OF_FRAGOFF_h, OF_FRAGOFF_l,
422 offsmf);
423 next = hdr6f->next;
424 data_offs += sizeof(ip6_header_fragment_t);
425 payload_len -= sizeof(ip6_header_fragment_t);
426 } else {
427 ident = 0;
428 offsmf = 0;
429 foff = 0;
430 next = hdr6->next;
431 }
[a17356fd]432
433 addr128_t src;
434 addr128_t dest;
435
436 addr128_t_be2host(hdr6->src_addr, src);
437 inet_addr_set6(src, &packet->src);
438
439 addr128_t_be2host(hdr6->dest_addr, dest);
440 inet_addr_set6(dest, &packet->dest);
441
442 packet->tos = 0;
[44c9ef4]443 packet->proto = next;
[a17356fd]444 packet->ttl = hdr6->hop_limit;
445 packet->ident = ident;
446
[44c9ef4]447 packet->df = 1;
448 packet->mf = (offsmf & BIT_V(uint16_t, OF_FLAG_M)) != 0;
449 packet->offs = foff * FRAG_OFFS_UNIT;
[a17356fd]450
451 packet->size = payload_len;
452 packet->data = calloc(packet->size, 1);
453 if (packet->data == NULL) {
454 log_msg(LOG_DEFAULT, LVL_WARN, "Out of memory.");
455 return ENOMEM;
456 }
457
[44c9ef4]458 memcpy(packet->data, (uint8_t *) data + data_offs, packet->size);
[a17356fd]459
460 return EOK;
461}
462
[1d94e21]463/** Encode NDP packet
464 *
465 * @param ndp NDP packet structure to be serialized
466 * @param dgram IPv6 datagram structure to be filled
467 *
468 * @return EOK on success
469 *
470 */
[a17356fd]471int ndp_pdu_encode(ndp_packet_t *ndp, inet_dgram_t *dgram)
472{
473 inet_addr_set6(ndp->sender_proto_addr, &dgram->src);
474 inet_addr_set6(ndp->target_proto_addr, &dgram->dest);
475 dgram->tos = 0;
476 dgram->size = sizeof(icmpv6_message_t) + sizeof(ndp_message_t);
477
478 dgram->data = calloc(1, dgram->size);
479 if (dgram->data == NULL)
480 return ENOMEM;
481
482 icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
483
484 icmpv6->type = ndp->opcode;
485 icmpv6->code = 0;
486 memset(icmpv6->un.ndp.reserved, 0, 3);
487
488 ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
489
490 if (ndp->opcode == ICMPV6_NEIGHBOUR_SOLICITATION) {
491 host2addr128_t_be(ndp->solicited_ip, message->target_address);
492 message->option = 1;
493 icmpv6->un.ndp.flags = 0;
494 } else {
495 host2addr128_t_be(ndp->sender_proto_addr, message->target_address);
496 message->option = 2;
497 icmpv6->un.ndp.flags = NDP_FLAG_OVERRIDE | NDP_FLAG_SOLICITED;
498 }
499
500 message->length = 1;
501 addr48(ndp->sender_hw_addr, message->mac);
502
[12df1f1]503 icmpv6_phdr_t phdr;
[a17356fd]504
505 host2addr128_t_be(ndp->sender_proto_addr, phdr.src_addr);
506 host2addr128_t_be(ndp->target_proto_addr, phdr.dest_addr);
507 phdr.length = host2uint32_t_be(dgram->size);
508 memset(phdr.zeroes, 0, 3);
509 phdr.next = IP_PROTO_ICMPV6;
510
511 uint16_t cs_phdr =
512 inet_checksum_calc(INET_CHECKSUM_INIT, &phdr,
[12df1f1]513 sizeof(icmpv6_phdr_t));
[a17356fd]514
515 uint16_t cs_all = inet_checksum_calc(cs_phdr, dgram->data,
516 dgram->size);
517
518 icmpv6->checksum = host2uint16_t_be(cs_all);
519
520 return EOK;
521}
522
[1d94e21]523/** Decode NDP packet
524 *
525 * @param dgram Incoming IPv6 datagram encapsulating NDP packet
526 * @param ndp NDP packet structure to be filled
527 *
528 * @return EOK on success
529 * @return EINVAL if the Datagram is invalid
530 *
531 */
[a17356fd]532int ndp_pdu_decode(inet_dgram_t *dgram, ndp_packet_t *ndp)
533{
[f023251]534 ip_ver_t src_ver = inet_addr_get(&dgram->src, NULL,
[a17356fd]535 &ndp->sender_proto_addr);
[f023251]536 if (src_ver != ip_v6)
[a17356fd]537 return EINVAL;
538
539 if (dgram->size < sizeof(icmpv6_message_t) + sizeof(ndp_message_t))
540 return EINVAL;
541
542 icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
543
544 ndp->opcode = icmpv6->type;
545
546 ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
547
548 addr128_t_be2host(message->target_address, ndp->target_proto_addr);
549 addr48(message->mac, ndp->sender_hw_addr);
550
551 return EOK;
[1d24ad3]552}
553
[ceba4bed]554/** @}
555 */
Note: See TracBrowser for help on using the repository browser.