1 /* $OpenBSD: ppp-deflate.c,v 1.17 2021/07/22 16:40:19 tb Exp $ */
2 /* $NetBSD: ppp-deflate.c,v 1.1 1996/03/15 02:28:09 paulus Exp $ */
3
4 /*
5 * ppp_deflate.c - interface the zlib procedures for Deflate compression
6 * and decompression (as used by gzip) to the PPP code.
7 * This version is for use with mbufs on BSD-derived systems.
8 *
9 * Copyright (c) 1989-2002 Paul Mackerras. All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in
20 * the documentation and/or other materials provided with the
21 * distribution.
22 *
23 * 3. The name(s) of the authors of this software must not be used to
24 * endorse or promote products derived from this software without
25 * prior written permission.
26 *
27 * 4. Redistributions of any form whatsoever must retain the following
28 * acknowledgment:
29 * "This product includes software developed by Paul Mackerras
30 * <paulus@samba.org>".
31 *
32 * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
33 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
34 * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
35 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
36 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
37 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
38 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
39 */
40
41 #include <sys/param.h>
42 #include <sys/systm.h>
43 #include <sys/mbuf.h>
44 #include <net/ppp_defs.h>
45 #include <lib/libz/zlib.h>
46
47 #define PACKETPTR struct mbuf *
48 #include <net/ppp-comp.h>
49
50 #if DO_DEFLATE
51
52 /*
53 * State for a Deflate (de)compressor.
54 */
55 struct deflate_state {
56 int seqno;
57 int w_size;
58 int unit;
59 int hdrlen;
60 int mru;
61 int debug;
62 z_stream strm;
63 struct compstat stats;
64 };
65
66 #define DEFLATE_OVHD 2 /* Deflate overhead/packet */
67
68 static void *zcalloc(void *, u_int items, u_int size);
69 static void zcfree(void *, void *ptr, u_int size);
70 static void *z_comp_alloc(u_char *options, int opt_len);
71 static void *z_decomp_alloc(u_char *options, int opt_len);
72 static void z_comp_free(void *state);
73 static void z_decomp_free(void *state);
74 static int z_comp_init(void *state, u_char *options, int opt_len,
75 int unit, int hdrlen, int debug);
76 static int z_decomp_init(void *state, u_char *options, int opt_len,
77 int unit, int hdrlen, int mru, int debug);
78 static int z_compress(void *state, struct mbuf **mret,
79 struct mbuf *mp, int slen, int maxolen);
80 static void z_incomp(void *state, struct mbuf *dmsg);
81 static int z_decompress(void *state, struct mbuf *cmp,
82 struct mbuf **dmpp);
83 static void z_comp_reset(void *state);
84 static void z_decomp_reset(void *state);
85 static void z_comp_stats(void *state, struct compstat *stats);
86
87 /*
88 * Procedures exported to if_ppp.c.
89 */
90 struct compressor ppp_deflate = {
91 CI_DEFLATE, /* compress_proto */
92 z_comp_alloc, /* comp_alloc */
93 z_comp_free, /* comp_free */
94 z_comp_init, /* comp_init */
95 z_comp_reset, /* comp_reset */
96 z_compress, /* compress */
97 z_comp_stats, /* comp_stat */
98 z_decomp_alloc, /* decomp_alloc */
99 z_decomp_free, /* decomp_free */
100 z_decomp_init, /* decomp_init */
101 z_decomp_reset, /* decomp_reset */
102 z_decompress, /* decompress */
103 z_incomp, /* incomp */
104 z_comp_stats, /* decomp_stat */
105 };
106
107 struct compressor ppp_deflate_draft = {
108 CI_DEFLATE_DRAFT, /* compress_proto */
109 z_comp_alloc, /* comp_alloc */
110 z_comp_free, /* comp_free */
111 z_comp_init, /* comp_init */
112 z_comp_reset, /* comp_reset */
113 z_compress, /* compress */
114 z_comp_stats, /* comp_stat */
115 z_decomp_alloc, /* decomp_alloc */
116 z_decomp_free, /* decomp_free */
117 z_decomp_init, /* decomp_init */
118 z_decomp_reset, /* decomp_reset */
119 z_decompress, /* decompress */
120 z_incomp, /* incomp */
121 z_comp_stats, /* decomp_stat */
122 };
123 /*
124 * Space allocation and freeing routines for use by zlib routines.
125 */
126 void *
zcalloc(void * notused,u_int items,u_int size)127 zcalloc(void *notused, u_int items, u_int size)
128 {
129 void *ptr;
130
131 ptr = mallocarray(items, size, M_DEVBUF, M_NOWAIT);
132 return ptr;
133 }
134
135 void
zcfree(void * notused,void * ptr,u_int size)136 zcfree(void *notused, void *ptr, u_int size)
137 {
138 free(ptr, M_DEVBUF, size);
139 }
140
141 /*
142 * Allocate space for a compressor.
143 */
144 static void *
z_comp_alloc(u_char * options,int opt_len)145 z_comp_alloc(u_char *options, int opt_len)
146 {
147 struct deflate_state *state;
148 int w_size;
149
150 if (opt_len != CILEN_DEFLATE
151 || (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT)
152 || options[1] != CILEN_DEFLATE
153 || DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL
154 || options[3] != DEFLATE_CHK_SEQUENCE)
155 return NULL;
156 w_size = DEFLATE_SIZE(options[2]);
157 if (w_size < DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE)
158 return NULL;
159
160 state = malloc(sizeof(*state), M_DEVBUF, M_NOWAIT);
161 if (state == NULL)
162 return NULL;
163
164 state->strm.next_in = NULL;
165 state->strm.zalloc = zcalloc;
166 state->strm.zfree = zcfree;
167 if (deflateInit2(&state->strm, Z_DEFAULT_COMPRESSION, DEFLATE_METHOD_VAL,
168 -w_size, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
169 free(state, M_DEVBUF, 0);
170 return NULL;
171 }
172
173 state->w_size = w_size;
174 bzero(&state->stats, sizeof(state->stats));
175 return (void *) state;
176 }
177
178 static void
z_comp_free(void * arg)179 z_comp_free(void *arg)
180 {
181 struct deflate_state *state = (struct deflate_state *) arg;
182
183 deflateEnd(&state->strm);
184 free(state, M_DEVBUF, 0);
185 }
186
187 static int
z_comp_init(void * arg,u_char * options,int opt_len,int unit,int hdrlen,int debug)188 z_comp_init(void *arg, u_char *options, int opt_len, int unit, int hdrlen,
189 int debug)
190 {
191 struct deflate_state *state = (struct deflate_state *) arg;
192
193 if (opt_len < CILEN_DEFLATE
194 || (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT)
195 || options[1] != CILEN_DEFLATE
196 || DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL
197 || DEFLATE_SIZE(options[2]) != state->w_size
198 || options[3] != DEFLATE_CHK_SEQUENCE)
199 return 0;
200
201 state->seqno = 0;
202 state->unit = unit;
203 state->hdrlen = hdrlen;
204 state->debug = debug;
205
206 deflateReset(&state->strm);
207
208 return 1;
209 }
210
211 static void
z_comp_reset(void * arg)212 z_comp_reset(void *arg)
213 {
214 struct deflate_state *state = (struct deflate_state *) arg;
215
216 state->seqno = 0;
217 deflateReset(&state->strm);
218 }
219
220 int
z_compress(void * arg,struct mbuf ** mret,struct mbuf * mp,int orig_len,int maxolen)221 z_compress(void *arg,
222 struct mbuf **mret, /* compressed packet (out) */
223 struct mbuf *mp, /* uncompressed packet (in) */
224 int orig_len, int maxolen)
225 {
226 struct deflate_state *state = (struct deflate_state *) arg;
227 u_char *rptr, *wptr;
228 int proto, olen, wspace, r, flush;
229 struct mbuf *m;
230
231 /*
232 * Check that the protocol is in the range we handle.
233 */
234 rptr = mtod(mp, u_char *);
235 proto = PPP_PROTOCOL(rptr);
236 if (proto > 0x3fff || proto == 0xfd || proto == 0xfb) {
237 *mret = NULL;
238 return orig_len;
239 }
240
241 /* Allocate one mbuf initially. */
242 if (maxolen > orig_len)
243 maxolen = orig_len;
244 MGET(m, M_DONTWAIT, MT_DATA);
245 *mret = m;
246 if (m != NULL) {
247 m->m_len = 0;
248 if (maxolen + state->hdrlen > MLEN)
249 MCLGET(m, M_DONTWAIT);
250 wspace = m_trailingspace(m);
251 if (state->hdrlen + PPP_HDRLEN + 2 < wspace) {
252 m->m_data += state->hdrlen;
253 wspace -= state->hdrlen;
254 }
255 wptr = mtod(m, u_char *);
256
257 /*
258 * Copy over the PPP header and store the 2-byte sequence number.
259 */
260 wptr[0] = PPP_ADDRESS(rptr);
261 wptr[1] = PPP_CONTROL(rptr);
262 wptr[2] = PPP_COMP >> 8;
263 wptr[3] = PPP_COMP;
264 wptr += PPP_HDRLEN;
265 wptr[0] = state->seqno >> 8;
266 wptr[1] = state->seqno;
267 wptr += 2;
268 state->strm.next_out = wptr;
269 state->strm.avail_out = wspace - (PPP_HDRLEN + 2);
270 } else {
271 state->strm.next_out = NULL;
272 state->strm.avail_out = 1000000;
273 wptr = NULL;
274 wspace = 0;
275 }
276 ++state->seqno;
277
278 rptr += (proto > 0xff)? 2: 3; /* skip 1st proto byte if 0 */
279 state->strm.next_in = rptr;
280 state->strm.avail_in = mtod(mp, u_char *) + mp->m_len - rptr;
281 mp = mp->m_next;
282 flush = (mp == NULL)? Z_SYNC_FLUSH: Z_NO_FLUSH;
283 olen = 0;
284 for (;;) {
285 r = deflate(&state->strm, flush);
286 if (r != Z_OK) {
287 printf("z_compress: deflate returned %d (%s)\n",
288 r, (state->strm.msg? state->strm.msg: ""));
289 break;
290 }
291 if (flush != Z_NO_FLUSH && state->strm.avail_out != 0)
292 break; /* all done */
293 if (state->strm.avail_in == 0 && mp != NULL) {
294 state->strm.next_in = mtod(mp, u_char *);
295 state->strm.avail_in = mp->m_len;
296 mp = mp->m_next;
297 if (mp == NULL)
298 flush = Z_SYNC_FLUSH;
299 }
300 if (state->strm.avail_out == 0) {
301 if (m != NULL) {
302 m->m_len = wspace;
303 olen += wspace;
304 MGET(m->m_next, M_DONTWAIT, MT_DATA);
305 m = m->m_next;
306 if (m != NULL) {
307 m->m_len = 0;
308 if (maxolen - olen > MLEN)
309 MCLGET(m, M_DONTWAIT);
310 state->strm.next_out = mtod(m, u_char *);
311 state->strm.avail_out = wspace = m_trailingspace(m);
312 }
313 }
314 if (m == NULL) {
315 state->strm.next_out = NULL;
316 state->strm.avail_out = 1000000;
317 }
318 }
319 }
320 if (m != NULL)
321 olen += (m->m_len = wspace - state->strm.avail_out);
322
323 /*
324 * See if we managed to reduce the size of the packet.
325 * If the compressor just gave us a single zero byte, it means
326 * the packet was incompressible.
327 */
328 if (m != NULL && olen < orig_len
329 && !(olen == PPP_HDRLEN + 3 && *wptr == 0)) {
330 state->stats.comp_bytes += olen;
331 state->stats.comp_packets++;
332 } else {
333 m_freemp(mret);
334
335 state->stats.inc_bytes += orig_len;
336 state->stats.inc_packets++;
337 olen = orig_len;
338 }
339 state->stats.unc_bytes += orig_len;
340 state->stats.unc_packets++;
341
342 return olen;
343 }
344
345 static void
z_comp_stats(void * arg,struct compstat * stats)346 z_comp_stats(void *arg, struct compstat *stats)
347 {
348 struct deflate_state *state = (struct deflate_state *) arg;
349 u_int out;
350
351 *stats = state->stats;
352 stats->ratio = stats->unc_bytes;
353 out = stats->comp_bytes + stats->inc_bytes;
354 if (stats->ratio <= 0x7ffffff)
355 stats->ratio <<= 8;
356 else
357 out >>= 8;
358 if (out != 0)
359 stats->ratio /= out;
360 }
361
362 /*
363 * Allocate space for a decompressor.
364 */
365 static void *
z_decomp_alloc(u_char * options,int opt_len)366 z_decomp_alloc(u_char *options, int opt_len)
367 {
368 struct deflate_state *state;
369 int w_size;
370
371 if (opt_len != CILEN_DEFLATE
372 || (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT)
373 || options[1] != CILEN_DEFLATE
374 || DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL
375 || options[3] != DEFLATE_CHK_SEQUENCE)
376 return NULL;
377 w_size = DEFLATE_SIZE(options[2]);
378 if (w_size < DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE)
379 return NULL;
380
381 state = malloc(sizeof(*state), M_DEVBUF, M_NOWAIT);
382 if (state == NULL)
383 return NULL;
384
385 state->strm.next_out = NULL;
386 state->strm.zalloc = zcalloc;
387 state->strm.zfree = zcfree;
388 if (inflateInit2(&state->strm, -w_size) != Z_OK) {
389 free(state, M_DEVBUF, 0);
390 return NULL;
391 }
392
393 state->w_size = w_size;
394 bzero(&state->stats, sizeof(state->stats));
395 return (void *) state;
396 }
397
398 static void
z_decomp_free(void * arg)399 z_decomp_free(void *arg)
400 {
401 struct deflate_state *state = (struct deflate_state *) arg;
402
403 inflateEnd(&state->strm);
404 free(state, M_DEVBUF, 0);
405 }
406
407 static int
z_decomp_init(void * arg,u_char * options,int opt_len,int unit,int hdrlen,int mru,int debug)408 z_decomp_init(void *arg, u_char *options, int opt_len, int unit, int hdrlen,
409 int mru, int debug)
410 {
411 struct deflate_state *state = (struct deflate_state *) arg;
412
413 if (opt_len < CILEN_DEFLATE
414 || (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT)
415 || options[1] != CILEN_DEFLATE
416 || DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL
417 || DEFLATE_SIZE(options[2]) != state->w_size
418 || options[3] != DEFLATE_CHK_SEQUENCE)
419 return 0;
420
421 state->seqno = 0;
422 state->unit = unit;
423 state->hdrlen = hdrlen;
424 state->debug = debug;
425 state->mru = mru;
426
427 inflateReset(&state->strm);
428
429 return 1;
430 }
431
432 static void
z_decomp_reset(void * arg)433 z_decomp_reset(void *arg)
434 {
435 struct deflate_state *state = (struct deflate_state *) arg;
436
437 state->seqno = 0;
438 inflateReset(&state->strm);
439 }
440
441 /*
442 * Decompress a Deflate-compressed packet.
443 *
444 * Because of patent problems, we return DECOMP_ERROR for errors
445 * found by inspecting the input data and for system problems, but
446 * DECOMP_FATALERROR for any errors which could possibly be said to
447 * be being detected "after" decompression. For DECOMP_ERROR,
448 * we can issue a CCP reset-request; for DECOMP_FATALERROR, we may be
449 * infringing a patent of Motorola's if we do, so we take CCP down
450 * instead.
451 *
452 * Given that the frame has the correct sequence number and a good FCS,
453 * errors such as invalid codes in the input most likely indicate a
454 * bug, so we return DECOMP_FATALERROR for them in order to turn off
455 * compression, even though they are detected by inspecting the input.
456 */
457 int
z_decompress(void * arg,struct mbuf * mi,struct mbuf ** mop)458 z_decompress(void *arg, struct mbuf *mi, struct mbuf **mop)
459 {
460 struct deflate_state *state = (struct deflate_state *) arg;
461 struct mbuf *mo, *mo_head;
462 u_char *rptr, *wptr;
463 int rlen, olen, ospace;
464 int seq, i, flush, r, decode_proto;
465 u_char hdr[PPP_HDRLEN + DEFLATE_OVHD];
466
467 *mop = NULL;
468 rptr = mtod(mi, u_char *);
469 rlen = mi->m_len;
470 for (i = 0; i < PPP_HDRLEN + DEFLATE_OVHD; ++i) {
471 while (rlen <= 0) {
472 mi = mi->m_next;
473 if (mi == NULL)
474 return DECOMP_ERROR;
475 rptr = mtod(mi, u_char *);
476 rlen = mi->m_len;
477 }
478 hdr[i] = *rptr++;
479 --rlen;
480 }
481
482 /* Check the sequence number. */
483 seq = (hdr[PPP_HDRLEN] << 8) + hdr[PPP_HDRLEN+1];
484 if (seq != state->seqno) {
485 if (state->debug)
486 printf("z_decompress%d: bad seq # %d, expected %d\n",
487 state->unit, seq, state->seqno);
488 return DECOMP_ERROR;
489 }
490 ++state->seqno;
491
492 /* Allocate an output mbuf. */
493 MGETHDR(mo, M_DONTWAIT, MT_DATA);
494 if (mo == NULL)
495 return DECOMP_ERROR;
496 mo_head = mo;
497 mo->m_len = 0;
498 mo->m_next = NULL;
499 MCLGET(mo, M_DONTWAIT);
500 ospace = m_trailingspace(mo);
501 if (state->hdrlen + PPP_HDRLEN < ospace) {
502 mo->m_data += state->hdrlen;
503 ospace -= state->hdrlen;
504 }
505
506 /*
507 * Fill in the first part of the PPP header. The protocol field
508 * comes from the decompressed data.
509 */
510 wptr = mtod(mo, u_char *);
511 wptr[0] = PPP_ADDRESS(hdr);
512 wptr[1] = PPP_CONTROL(hdr);
513 wptr[2] = 0;
514
515 /*
516 * Set up to call inflate. We set avail_out to 1 initially so we can
517 * look at the first byte of the output and decide whether we have
518 * a 1-byte or 2-byte protocol field.
519 */
520 state->strm.next_in = rptr;
521 state->strm.avail_in = rlen;
522 mi = mi->m_next;
523 flush = (mi == NULL)? Z_SYNC_FLUSH: Z_NO_FLUSH;
524 rlen += PPP_HDRLEN + DEFLATE_OVHD;
525 state->strm.next_out = wptr + 3;
526 state->strm.avail_out = 1;
527 decode_proto = 1;
528 olen = PPP_HDRLEN;
529
530 /*
531 * Call inflate, supplying more input or output as needed.
532 */
533 for (;;) {
534 r = inflate(&state->strm, flush);
535 if (r != Z_OK) {
536 #ifndef DEFLATE_DEBUG
537 if (state->debug)
538 #endif
539 printf("z_decompress%d: inflate returned %d (%s)\n",
540 state->unit, r, (state->strm.msg? state->strm.msg: ""));
541 m_freem(mo_head);
542 return DECOMP_FATALERROR;
543 }
544 if (flush != Z_NO_FLUSH && state->strm.avail_out != 0)
545 break; /* all done */
546 if (state->strm.avail_in == 0 && mi != NULL) {
547 state->strm.next_in = mtod(mi, u_char *);
548 state->strm.avail_in = mi->m_len;
549 rlen += mi->m_len;
550 mi = mi->m_next;
551 if (mi == NULL)
552 flush = Z_SYNC_FLUSH;
553 }
554 if (state->strm.avail_out == 0) {
555 if (decode_proto) {
556 state->strm.avail_out = ospace - PPP_HDRLEN;
557 if ((wptr[3] & 1) == 0) {
558 /* 2-byte protocol field */
559 wptr[2] = wptr[3];
560 --state->strm.next_out;
561 ++state->strm.avail_out;
562 --olen;
563 }
564 decode_proto = 0;
565 } else {
566 mo->m_len = ospace;
567 olen += ospace;
568 MGET(mo->m_next, M_DONTWAIT, MT_DATA);
569 mo = mo->m_next;
570 if (mo == NULL) {
571 m_freem(mo_head);
572 return DECOMP_ERROR;
573 }
574 MCLGET(mo, M_DONTWAIT);
575 state->strm.next_out = mtod(mo, u_char *);
576 state->strm.avail_out = ospace = m_trailingspace(mo);
577 }
578 }
579 }
580 if (decode_proto) {
581 m_freem(mo_head);
582 return DECOMP_ERROR;
583 }
584 olen += (mo->m_len = ospace - state->strm.avail_out);
585 #ifdef DEFLATE_DEBUG
586 if (olen > state->mru + PPP_HDRLEN)
587 printf("ppp_deflate%d: exceeded mru (%d > %d)\n",
588 state->unit, olen, state->mru + PPP_HDRLEN);
589 #endif
590
591 state->stats.unc_bytes += olen;
592 state->stats.unc_packets++;
593 state->stats.comp_bytes += rlen;
594 state->stats.comp_packets++;
595
596 *mop = mo_head;
597 return DECOMP_OK;
598 }
599
600 /*
601 * Incompressible data has arrived - add it to the history.
602 */
603 static void
z_incomp(void * arg,struct mbuf * mi)604 z_incomp(void *arg, struct mbuf *mi)
605 {
606 struct deflate_state *state = (struct deflate_state *) arg;
607 u_char *rptr;
608 int rlen, proto, r;
609
610 /*
611 * Check that the protocol is one we handle.
612 */
613 rptr = mtod(mi, u_char *);
614 proto = PPP_PROTOCOL(rptr);
615 if (proto > 0x3fff || proto == 0xfd || proto == 0xfb)
616 return;
617
618 ++state->seqno;
619
620 /*
621 * Iterate through the mbufs, adding the characters in them
622 * to the decompressor's history. For the first mbuf, we start
623 * at the either the 1st or 2nd byte of the protocol field,
624 * depending on whether the protocol value is compressible.
625 */
626 rlen = mi->m_len;
627 state->strm.next_in = rptr + 3;
628 state->strm.avail_in = rlen - 3;
629 if (proto > 0xff) {
630 --state->strm.next_in;
631 ++state->strm.avail_in;
632 }
633 for (;;) {
634 r = inflateInit(&state->strm);
635 if (r != Z_OK) {
636 /* gak! */
637 #ifndef DEFLATE_DEBUG
638 if (state->debug)
639 #endif
640 printf("z_incomp%d: inflateIncomp returned %d (%s)\n",
641 state->unit, r, (state->strm.msg? state->strm.msg: ""));
642 return;
643 }
644 mi = mi->m_next;
645 if (mi == NULL)
646 break;
647 state->strm.next_in = mtod(mi, u_char *);
648 state->strm.avail_in = mi->m_len;
649 rlen += mi->m_len;
650 }
651
652 /*
653 * Update stats.
654 */
655 state->stats.inc_bytes += rlen;
656 state->stats.inc_packets++;
657 state->stats.unc_bytes += rlen;
658 state->stats.unc_packets++;
659 }
660
661 #endif /* DO_DEFLATE */
662