root/webserver/example/freeRTOS/Demo/Common/ethernet/lwIP/netif/ppp/vj.c

Revision 14, 17.4 kB (checked in by phil, 15 years ago)

added unmodified FreeRTOS package V5.4.1 with only web srv demo source for LPC2368 for CrossWorks?

Line 
1 /*
2  * Routines to compress and uncompess tcp packets (for transmission
3  * over low speed serial lines.
4  *
5  * Copyright (c) 1989 Regents of the University of California.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms are permitted
9  * provided that the above copyright notice and this paragraph are
10  * duplicated in all such forms and that any documentation,
11  * advertising materials, and other materials related to such
12  * distribution and use acknowledge that the software was developed
13  * by the University of California, Berkeley.  The name of the
14  * University may not be used to endorse or promote products derived
15  * from this software without specific prior written permission.
16  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
18  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19  *
20  *      Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
21  *      - Initial distribution.
22  *
23  * Modified June 1993 by Paul Mackerras, paulus@cs.anu.edu.au,
24  * so that the entire packet being decompressed doesn't have
25  * to be in contiguous memory (just the compressed header).
26  *
27  * Modified March 1998 by Guy Lancaster, glanca@gesn.com,
28  * for a 16 bit processor.
29  */
30
31 #include <string.h>
32
33 #include "ppp.h"
34 #include "vj.h"
35 #include "pppdebug.h"
36
37 #if VJ_SUPPORT > 0
38
39 #if LINK_STATS
40 #define INCR(counter) ++comp->stats.counter
41 #else
42 #define INCR(counter)
43 #endif
44
45 #if defined(NO_CHAR_BITFIELDS)
46 #define getip_hl(base)  ((base).ip_hl_v&0xf)
47 #define getth_off(base) (((base).th_x2_off&0xf0)>>4)
48 #else
49 #define getip_hl(base)  ((base).ip_hl)
50 #define getth_off(base) ((base).th_off)
51 #endif
52
53 void vj_compress_init(struct vjcompress *comp)
54 {
55         register u_int i;
56         register struct cstate *tstate = comp->tstate;
57        
58 #if MAX_SLOTS == 0
59         memset((char *)comp, 0, sizeof(*comp));
60 #endif
61         comp->maxSlotIndex = MAX_SLOTS - 1;
62         comp->compressSlot = 0;         /* Disable slot ID compression by default. */
63         for (i = MAX_SLOTS - 1; i > 0; --i) {
64                 tstate[i].cs_id = i;
65                 tstate[i].cs_next = &tstate[i - 1];
66         }
67         tstate[0].cs_next = &tstate[MAX_SLOTS - 1];
68         tstate[0].cs_id = 0;
69         comp->last_cs = &tstate[0];
70         comp->last_recv = 255;
71         comp->last_xmit = 255;
72         comp->flags = VJF_TOSS;
73 }
74
75
76 /* ENCODE encodes a number that is known to be non-zero.  ENCODEZ
77  * checks for zero (since zero has to be encoded in the long, 3 byte
78  * form).
79  */
80 #define ENCODE(n) { \
81         if ((u_short)(n) >= 256) { \
82                 *cp++ = 0; \
83                 cp[1] = (n); \
84                 cp[0] = (n) >> 8; \
85                 cp += 2; \
86         } else { \
87                 *cp++ = (n); \
88         } \
89 }
90 #define ENCODEZ(n) { \
91         if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
92                 *cp++ = 0; \
93                 cp[1] = (n); \
94                 cp[0] = (n) >> 8; \
95                 cp += 2; \
96         } else { \
97                 *cp++ = (n); \
98         } \
99 }
100
101 #define DECODEL(f) { \
102         if (*cp == 0) {\
103                 u32_t tmp = ntohl(f) + ((cp[1] << 8) | cp[2]); \
104                 (f) = htonl(tmp); \
105                 cp += 3; \
106         } else { \
107                 u32_t tmp = ntohl(f) + (u32_t)*cp++; \
108                 (f) = htonl(tmp); \
109         } \
110 }
111
112 #define DECODES(f) { \
113         if (*cp == 0) {\
114                 u_short tmp = ntohs(f) + (((u_short)cp[1] << 8) | cp[2]); \
115                 (f) = htons(tmp); \
116                 cp += 3; \
117         } else { \
118                 u_short tmp = ntohs(f) + (u_short)*cp++; \
119                 (f) = htons(tmp); \
120         } \
121 }
122
123 #define DECODEU(f) { \
124         if (*cp == 0) {\
125                 (f) = htons(((u_short)cp[1] << 8) | cp[2]); \
126                 cp += 3; \
127         } else { \
128                 (f) = htons((u_short)*cp++); \
129         } \
130 }
131
132 /*
133  * vj_compress_tcp - Attempt to do Van Jacobsen header compression on a
134  * packet.  This assumes that nb and comp are not null and that the first
135  * buffer of the chain contains a valid IP header.
136  * Return the VJ type code indicating whether or not the packet was
137  * compressed.
138  */
139 u_int vj_compress_tcp(
140         struct vjcompress *comp,
141         struct pbuf *pb
142 )
143 {
144         register struct ip *ip = (struct ip *)pb->payload;
145         register struct cstate *cs = comp->last_cs->cs_next;
146         register u_short hlen = getip_hl(*ip);
147         register struct tcphdr *oth;
148         register struct tcphdr *th;
149         register u_short deltaS, deltaA;
150         register u_long deltaL;
151         register u_int changes = 0;
152         u_char new_seq[16];
153         register u_char *cp = new_seq;
154
155         /*     
156          * Check that the packet is IP proto TCP.
157          */
158         if (ip->ip_p != IPPROTO_TCP)
159                 return (TYPE_IP);
160                
161         /*
162          * Bail if this is an IP fragment or if the TCP packet isn't
163          * `compressible' (i.e., ACK isn't set or some other control bit is
164          * set). 
165          */
166         if ((ip->ip_off & htons(0x3fff)) || pb->tot_len < 40)
167                 return (TYPE_IP);
168         th = (struct tcphdr *)&((long *)ip)[hlen];
169         if ((th->th_flags & (TCP_SYN|TCP_FIN|TCP_RST|TCP_ACK)) != TCP_ACK)
170                 return (TYPE_IP);
171                
172         /*
173          * Packet is compressible -- we're going to send either a
174          * COMPRESSED_TCP or UNCOMPRESSED_TCP packet.  Either way we need
175          * to locate (or create) the connection state.  Special case the
176          * most recently used connection since it's most likely to be used
177          * again & we don't have to do any reordering if it's used.
178          */
179         INCR(vjs_packets);
180         if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr
181                         || ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr
182                         || *(long *)th != ((long *)&cs->cs_ip)[getip_hl(cs->cs_ip)]) {
183                 /*
184                  * Wasn't the first -- search for it.
185                  *
186                  * States are kept in a circularly linked list with
187                  * last_cs pointing to the end of the list.  The
188                  * list is kept in lru order by moving a state to the
189                  * head of the list whenever it is referenced.  Since
190                  * the list is short and, empirically, the connection
191                  * we want is almost always near the front, we locate
192                  * states via linear search.  If we don't find a state
193                  * for the datagram, the oldest state is (re-)used.
194                  */
195                 register struct cstate *lcs;
196                 register struct cstate *lastcs = comp->last_cs;
197                
198                 do {
199                         lcs = cs; cs = cs->cs_next;
200                         INCR(vjs_searches);
201                         if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr
202                                         && ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr
203                                         && *(long *)th == ((long *)&cs->cs_ip)[getip_hl(cs->cs_ip)])
204                                 goto found;
205                 } while (cs != lastcs);
206                
207                 /*
208                  * Didn't find it -- re-use oldest cstate.  Send an
209                  * uncompressed packet that tells the other side what
210                  * connection number we're using for this conversation.
211                  * Note that since the state list is circular, the oldest
212                  * state points to the newest and we only need to set
213                  * last_cs to update the lru linkage.
214                  */
215                 INCR(vjs_misses);
216                 comp->last_cs = lcs;
217                 hlen += getth_off(*th);
218                 hlen <<= 2;
219                 /* Check that the IP/TCP headers are contained in the first buffer. */
220                 if (hlen > pb->len)
221                         return (TYPE_IP);
222                 goto uncompressed;
223                
224                 found:
225                 /*
226                  * Found it -- move to the front on the connection list.
227                  */
228                 if (cs == lastcs)
229                         comp->last_cs = lcs;
230                 else {
231                         lcs->cs_next = cs->cs_next;
232                         cs->cs_next = lastcs->cs_next;
233                         lastcs->cs_next = cs;
234                 }
235         }
236        
237         oth = (struct tcphdr *)&((long *)&cs->cs_ip)[hlen];
238         deltaS = hlen;
239         hlen += getth_off(*th);
240         hlen <<= 2;
241         /* Check that the IP/TCP headers are contained in the first buffer. */
242         if (hlen > pb->len) {
243                 PPPDEBUG((LOG_INFO, "vj_compress_tcp: header len %d spans buffers\n",
244                                         hlen));
245                 return (TYPE_IP);
246         }
247        
248         /*
249          * Make sure that only what we expect to change changed. The first
250          * line of the `if' checks the IP protocol version, header length &
251          * type of service.  The 2nd line checks the "Don't fragment" bit.
252          * The 3rd line checks the time-to-live and protocol (the protocol
253          * check is unnecessary but costless).  The 4th line checks the TCP
254          * header length.  The 5th line checks IP options, if any.  The 6th
255          * line checks TCP options, if any.  If any of these things are
256          * different between the previous & current datagram, we send the
257          * current datagram `uncompressed'.
258          */
259         if (((u_short *)ip)[0] != ((u_short *)&cs->cs_ip)[0]
260                         || ((u_short *)ip)[3] != ((u_short *)&cs->cs_ip)[3]
261                         || ((u_short *)ip)[4] != ((u_short *)&cs->cs_ip)[4]
262                         || getth_off(*th) != getth_off(*oth)
263                         || (deltaS > 5 && BCMP(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2))
264                         || (getth_off(*th) > 5 && BCMP(th + 1, oth + 1, (getth_off(*th) - 5) << 2)))
265                 goto uncompressed;
266        
267         /*
268          * Figure out which of the changing fields changed.  The
269          * receiver expects changes in the order: urgent, window,
270          * ack, seq (the order minimizes the number of temporaries
271          * needed in this section of code).
272          */
273         if (th->th_flags & TCP_URG) {
274                 deltaS = ntohs(th->th_urp);
275                 ENCODEZ(deltaS);
276                 changes |= NEW_U;
277         } else if (th->th_urp != oth->th_urp)
278                 /* argh! URG not set but urp changed -- a sensible
279                  * implementation should never do this but RFC793
280                  * doesn't prohibit the change so we have to deal
281                  * with it. */
282                 goto uncompressed;
283        
284         if ((deltaS = (u_short)(ntohs(th->th_win) - ntohs(oth->th_win))) != 0) {
285                 ENCODE(deltaS);
286                 changes |= NEW_W;
287         }
288        
289         if ((deltaL = ntohl(th->th_ack) - ntohl(oth->th_ack)) != 0) {
290                 if (deltaL > 0xffff)
291                         goto uncompressed;
292                 deltaA = (u_short)deltaL;
293                 ENCODE(deltaA);
294                 changes |= NEW_A;
295         }
296        
297         if ((deltaL = ntohl(th->th_seq) - ntohl(oth->th_seq)) != 0) {
298                 if (deltaL > 0xffff)
299                         goto uncompressed;
300                 deltaS = (u_short)deltaL;
301                 ENCODE(deltaS);
302                 changes |= NEW_S;
303         }
304        
305         switch(changes) {
306        
307         case 0:
308                 /*
309                  * Nothing changed. If this packet contains data and the
310                  * last one didn't, this is probably a data packet following
311                  * an ack (normal on an interactive connection) and we send
312                  * it compressed.  Otherwise it's probably a retransmit,
313                  * retransmitted ack or window probe.  Send it uncompressed
314                  * in case the other side missed the compressed version.
315                  */
316                 if (ip->ip_len != cs->cs_ip.ip_len &&
317                         ntohs(cs->cs_ip.ip_len) == hlen)
318                 break;
319        
320         /* (fall through) */
321        
322         case SPECIAL_I:
323         case SPECIAL_D:
324                 /*
325                  * actual changes match one of our special case encodings --
326                  * send packet uncompressed.
327                  */
328                 goto uncompressed;
329        
330         case NEW_S|NEW_A:
331                 if (deltaS == deltaA && deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
332                         /* special case for echoed terminal traffic */
333                         changes = SPECIAL_I;
334                         cp = new_seq;
335                 }
336                 break;
337        
338         case NEW_S:
339                 if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
340                         /* special case for data xfer */
341                         changes = SPECIAL_D;
342                         cp = new_seq;
343                 }
344                 break;
345         }
346        
347         deltaS = (u_short)(ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id));
348         if (deltaS != 1) {
349                 ENCODEZ(deltaS);
350                 changes |= NEW_I;
351         }
352         if (th->th_flags & TCP_PSH)
353         changes |= TCP_PUSH_BIT;
354         /*
355          * Grab the cksum before we overwrite it below.  Then update our
356          * state with this packet's header.
357          */
358         deltaA = ntohs(th->th_sum);
359         BCOPY(ip, &cs->cs_ip, hlen);
360        
361         /*
362          * We want to use the original packet as our compressed packet.
363          * (cp - new_seq) is the number of bytes we need for compressed
364          * sequence numbers.  In addition we need one byte for the change
365          * mask, one for the connection id and two for the tcp checksum.
366          * So, (cp - new_seq) + 4 bytes of header are needed.  hlen is how
367          * many bytes of the original packet to toss so subtract the two to
368          * get the new packet size.
369          */
370         deltaS = (u_short)(cp - new_seq);
371         if (!comp->compressSlot || comp->last_xmit != cs->cs_id) {
372                 comp->last_xmit = cs->cs_id;
373                 hlen -= deltaS + 4;
374                 pbuf_header(pb, -hlen);
375                 cp = (u_char *)pb->payload;
376                 *cp++ = changes | NEW_C;
377                 *cp++ = cs->cs_id;
378         } else {
379                 hlen -= deltaS + 3;
380                 pbuf_header(pb, -hlen);
381                 cp = (u_char *)pb->payload;
382                 *cp++ = changes;
383         }
384         *cp++ = deltaA >> 8;
385         *cp++ = deltaA;
386         BCOPY(new_seq, cp, deltaS);
387         INCR(vjs_compressed);
388         return (TYPE_COMPRESSED_TCP);
389
390         /*
391          * Update connection state cs & send uncompressed packet (that is,
392          * a regular ip/tcp packet but with the 'conversation id' we hope
393          * to use on future compressed packets in the protocol field).
394          */
395 uncompressed:
396         BCOPY(ip, &cs->cs_ip, hlen);
397         ip->ip_p = cs->cs_id;
398         comp->last_xmit = cs->cs_id;
399         return (TYPE_UNCOMPRESSED_TCP);
400 }
401
402 /*
403  * Called when we may have missed a packet.
404  */
405 void vj_uncompress_err(struct vjcompress *comp)
406 {
407     comp->flags |= VJF_TOSS;
408         INCR(vjs_errorin);
409 }
410
411 /*
412  * "Uncompress" a packet of type TYPE_UNCOMPRESSED_TCP.
413  * Return 0 on success, -1 on failure.
414  */
415 int vj_uncompress_uncomp(
416         struct pbuf *nb,
417         struct vjcompress *comp
418 )
419 {
420         register u_int hlen;
421         register struct cstate *cs;
422         register struct ip *ip;
423        
424         ip = (struct ip *)nb->payload;
425         hlen = getip_hl(*ip) << 2;
426         if (ip->ip_p >= MAX_SLOTS
427                         || hlen + sizeof(struct tcphdr) > nb->len
428                         || (hlen += getth_off(*((struct tcphdr *)&((char *)ip)[hlen])) << 2)
429                             > nb->len
430                         || hlen > MAX_HDR) {
431                 PPPDEBUG((LOG_INFO, "vj_uncompress_uncomp: bad cid=%d, hlen=%d buflen=%d\n",
432                                         ip->ip_p, hlen, nb->len));
433                 comp->flags |= VJF_TOSS;
434                 INCR(vjs_errorin);
435                 return -1;
436         }
437         cs = &comp->rstate[comp->last_recv = ip->ip_p];
438         comp->flags &=~ VJF_TOSS;
439         ip->ip_p = IPPROTO_TCP;
440         BCOPY(ip, &cs->cs_ip, hlen);
441         cs->cs_hlen = hlen;
442         INCR(vjs_uncompressedin);
443         return 0;
444 }
445
446 /*
447  * Uncompress a packet of type TYPE_COMPRESSED_TCP.
448  * The packet is composed of a buffer chain and the first buffer
449  * must contain an accurate chain length.
450  * The first buffer must include the entire compressed TCP/IP header.
451  * This procedure replaces the compressed header with the uncompressed
452  * header and returns the length of the VJ header.
453  */
454 int vj_uncompress_tcp(
455         struct pbuf **nb,
456         struct vjcompress *comp
457 )
458 {
459         u_char *cp;
460         struct tcphdr *th;
461         struct cstate *cs;
462         u_short *bp;
463         struct pbuf *n0 = *nb;
464         u32_t tmp;
465         u_int vjlen, hlen, changes;
466        
467         INCR(vjs_compressedin);
468         cp = (u_char *)n0->payload;
469         changes = *cp++;
470         if (changes & NEW_C) {
471                 /*
472                  * Make sure the state index is in range, then grab the state.
473                  * If we have a good state index, clear the 'discard' flag.
474                  */
475                 if (*cp >= MAX_SLOTS) {
476                         PPPDEBUG((LOG_INFO, "vj_uncompress_tcp: bad cid=%d\n", *cp));
477                         goto bad;
478                 }
479                
480                 comp->flags &=~ VJF_TOSS;
481                 comp->last_recv = *cp++;
482         } else {
483                 /*
484                  * this packet has an implicit state index.  If we've
485                  * had a line error since the last time we got an
486                  * explicit state index, we have to toss the packet.
487                  */
488                 if (comp->flags & VJF_TOSS) {
489                         PPPDEBUG((LOG_INFO, "vj_uncompress_tcp: tossing\n"));
490                         INCR(vjs_tossed);
491                         return (-1);
492                 }
493         }
494         cs = &comp->rstate[comp->last_recv];
495         hlen = getip_hl(cs->cs_ip) << 2;
496         th = (struct tcphdr *)&((u_char *)&cs->cs_ip)[hlen];
497         th->th_sum = htons((*cp << 8) | cp[1]);
498         cp += 2;
499         if (changes & TCP_PUSH_BIT)
500                 th->th_flags |= TCP_PSH;
501         else
502                 th->th_flags &=~ TCP_PSH;
503        
504         switch (changes & SPECIALS_MASK) {
505         case SPECIAL_I:
506                 {
507                         register u32_t i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
508                         /* some compilers can't nest inline assembler.. */
509                         tmp = ntohl(th->th_ack) + i;
510                         th->th_ack = htonl(tmp);
511                         tmp = ntohl(th->th_seq) + i;
512                         th->th_seq = htonl(tmp);
513                 }
514                 break;
515        
516         case SPECIAL_D:
517                 /* some compilers can't nest inline assembler.. */
518                 tmp = ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
519                 th->th_seq = htonl(tmp);
520                 break;
521        
522         default:
523                 if (changes & NEW_U) {
524                         th->th_flags |= TCP_URG;
525                         DECODEU(th->th_urp);
526                 } else
527                         th->th_flags &=~ TCP_URG;
528                 if (changes & NEW_W)
529                         DECODES(th->th_win);
530                 if (changes & NEW_A)
531                         DECODEL(th->th_ack);
532                 if (changes & NEW_S)
533                         DECODEL(th->th_seq);
534                 break;
535         }
536         if (changes & NEW_I) {
537                 DECODES(cs->cs_ip.ip_id);
538         } else {
539                 cs->cs_ip.ip_id = ntohs(cs->cs_ip.ip_id) + 1;
540                 cs->cs_ip.ip_id = htons(cs->cs_ip.ip_id);
541         }
542        
543         /*
544          * At this point, cp points to the first byte of data in the
545          * packet.  Fill in the IP total length and update the IP
546          * header checksum.
547          */
548         vjlen = (u_short)(cp - (u_char*)n0->payload);
549         if (n0->len < vjlen) {
550                 /*
551                  * We must have dropped some characters (crc should detect
552                  * this but the old slip framing won't)
553                  */
554                 PPPDEBUG((LOG_INFO, "vj_uncompress_tcp: head buffer %d too short %d\n",
555                                   n0->len, vjlen));
556                 goto bad;
557         }
558        
559 #if BYTE_ORDER == LITTLE_ENDIAN
560         tmp = n0->tot_len - vjlen + cs->cs_hlen;
561         cs->cs_ip.ip_len = htons(tmp);
562 #else
563         cs->cs_ip.ip_len = htons(n0->tot_len - vjlen + cs->cs_hlen);
564 #endif
565        
566         /* recompute the ip header checksum */
567         bp = (u_short *) &cs->cs_ip;
568         cs->cs_ip.ip_sum = 0;
569         for (tmp = 0; hlen > 0; hlen -= 2)
570                 tmp += *bp++;
571         tmp = (tmp & 0xffff) + (tmp >> 16);
572         tmp = (tmp & 0xffff) + (tmp >> 16);
573         cs->cs_ip.ip_sum = (u_short)(~tmp);
574        
575         /* Remove the compressed header and prepend the uncompressed header. */
576         pbuf_header(n0, -vjlen);
577
578         if(MEM_ALIGN(n0->payload) != n0->payload) {
579                 struct pbuf *np, *q;
580                 u8_t *bufptr;
581
582                 np = pbuf_alloc(PBUF_RAW, n0->len + cs->cs_hlen, PBUF_POOL);
583                 if(!np) {
584                         PPPDEBUG((LOG_WARNING, "vj_uncompress_tcp: realign failed\n"));
585                         *nb = NULL;
586                         goto bad;
587                 }
588
589                 pbuf_header(np, -cs->cs_hlen);
590
591                 bufptr = n0->payload;
592                 for(q = np; q != NULL; q = q->next) {
593                         memcpy(q->payload, bufptr, q->len);
594                         bufptr += q->len;
595                 }
596
597                 if(n0->next) {
598                         pbuf_chain(np, n0->next);
599                         pbuf_dechain(n0);
600                 }
601                 pbuf_free(n0);
602                 n0 = np;
603         }
604
605         if(pbuf_header(n0, cs->cs_hlen)) {
606                 struct pbuf *np;
607
608                 LWIP_ASSERT("vj_uncompress_tcp: cs->cs_hlen <= PBUF_POOL_BUFSIZE", cs->cs_hlen <= PBUF_POOL_BUFSIZE);
609                 np = pbuf_alloc(PBUF_RAW, cs->cs_hlen, PBUF_POOL);
610                 if(!np) {
611                         PPPDEBUG((LOG_WARNING, "vj_uncompress_tcp: prepend failed\n"));
612                         *nb = NULL;
613                         goto bad;
614                 }
615                 pbuf_cat(np, n0);
616                 n0 = np;
617         }
618         LWIP_ASSERT("n0->len >= cs->cs_hlen", n0->len >= cs->cs_hlen);
619         memcpy(n0->payload, &cs->cs_ip, cs->cs_hlen);
620
621         *nb = n0;
622
623         return vjlen;
624        
625 bad:
626         comp->flags |= VJF_TOSS;
627         INCR(vjs_errorin);
628         return (-1);
629 }
630
631 #endif
632
633
Note: See TracBrowser for help on using the browser.