ccp.c revision 45193
1/* 2 * PPP Compression Control Protocol (CCP) Module 3 * 4 * Written by Toshiharu OHNO (tony-o@iij.ad.jp) 5 * 6 * Copyright (C) 1994, Internet Initiative Japan, Inc. All rights reserverd. 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 Internet Initiative Japan, Inc. The name of the 14 * IIJ 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 * $Id: ccp.c,v 1.44 1999/03/11 01:49:15 brian Exp $ 21 * 22 * TODO: 23 * o Support other compression protocols 24 */ 25#include <sys/param.h> 26#include <netinet/in.h> 27#include <netinet/in_systm.h> 28#include <netinet/ip.h> 29#include <sys/un.h> 30 31#include <stdio.h> 32#include <stdlib.h> 33#include <string.h> 34#include <termios.h> 35 36#include "defs.h" 37#include "command.h" 38#include "mbuf.h" 39#include "log.h" 40#include "timer.h" 41#include "fsm.h" 42#include "lcpproto.h" 43#include "lcp.h" 44#include "ccp.h" 45#include "pred.h" 46#include "deflate.h" 47#include "throughput.h" 48#include "iplist.h" 49#include "slcompress.h" 50#include "lqr.h" 51#include "hdlc.h" 52#include "ipcp.h" 53#include "filter.h" 54#include "descriptor.h" 55#include "prompt.h" 56#include "link.h" 57#include "mp.h" 58#include "async.h" 59#include "physical.h" 60#ifndef NORADIUS 61#include "radius.h" 62#endif 63#include "bundle.h" 64 65static void CcpSendConfigReq(struct fsm *); 66static void CcpSentTerminateReq(struct fsm *); 67static void CcpSendTerminateAck(struct fsm *, u_char); 68static void CcpDecodeConfig(struct fsm *, u_char *, int, int, 69 struct fsm_decode *); 70static void CcpLayerStart(struct fsm *); 71static void CcpLayerFinish(struct fsm *); 72static int CcpLayerUp(struct fsm *); 73static void CcpLayerDown(struct fsm *); 74static void CcpInitRestartCounter(struct fsm *, int); 75static void CcpRecvResetReq(struct fsm *); 76static void CcpRecvResetAck(struct fsm *, u_char); 77 78static struct fsm_callbacks ccp_Callbacks = { 79 CcpLayerUp, 80 CcpLayerDown, 81 CcpLayerStart, 82 CcpLayerFinish, 83 CcpInitRestartCounter, 84 CcpSendConfigReq, 85 CcpSentTerminateReq, 86 CcpSendTerminateAck, 87 CcpDecodeConfig, 88 CcpRecvResetReq, 89 CcpRecvResetAck 90}; 91 92static const char *ccp_TimerNames[] = 93 {"CCP restart", "CCP openmode", "CCP stopped"}; 94 95static char const *cftypes[] = { 96 /* Check out the latest ``Compression Control Protocol'' rfc (rfc1962.txt) */ 97 "OUI", /* 0: OUI */ 98 "PRED1", /* 1: Predictor type 1 */ 99 "PRED2", /* 2: Predictor type 2 */ 100 "PUDDLE", /* 3: Puddle Jumber */ 101 "???", "???", "???", "???", "???", "???", 102 "???", "???", "???", "???", "???", "???", 103 "HWPPC", /* 16: Hewlett-Packard PPC */ 104 "STAC", /* 17: Stac Electronics LZS (rfc1974) */ 105 "MPPC", /* 18: Microsoft PPC (rfc2118) */ 106 "GAND", /* 19: Gandalf FZA (rfc1993) */ 107 "V42BIS", /* 20: ARG->DATA.42bis compression */ 108 "BSD", /* 21: BSD LZW Compress */ 109 "???", 110 "LZS-DCP", /* 23: LZS-DCP Compression Protocol (rfc1967) */ 111 "MAGNALINK/DEFLATE", /* 24: Magnalink Variable Resource (rfc1975) */ 112 /* 24: Deflate (according to pppd-2.3.*) */ 113 "DCE", /* 25: Data Circuit-Terminating Equip (rfc1976) */ 114 "DEFLATE", /* 26: Deflate (rfc1979) */ 115}; 116 117#define NCFTYPES (sizeof cftypes/sizeof cftypes[0]) 118 119static const char * 120protoname(int proto) 121{ 122 if (proto < 0 || proto > NCFTYPES) 123 return "none"; 124 return cftypes[proto]; 125} 126 127/* We support these algorithms, and Req them in the given order */ 128static const struct ccp_algorithm *algorithm[] = { 129 &DeflateAlgorithm, 130 &Pred1Algorithm, 131 &PppdDeflateAlgorithm 132}; 133 134#define NALGORITHMS (sizeof algorithm/sizeof algorithm[0]) 135 136int 137ccp_ReportStatus(struct cmdargs const *arg) 138{ 139 struct link *l; 140 struct ccp *ccp; 141 142 l = command_ChooseLink(arg); 143 ccp = &l->ccp; 144 145 prompt_Printf(arg->prompt, "%s: %s [%s]\n", l->name, ccp->fsm.name, 146 State2Nam(ccp->fsm.state)); 147 prompt_Printf(arg->prompt, " My protocol = %s, His protocol = %s\n", 148 protoname(ccp->my_proto), protoname(ccp->his_proto)); 149 prompt_Printf(arg->prompt, " Output: %ld --> %ld, Input: %ld --> %ld\n", 150 ccp->uncompout, ccp->compout, 151 ccp->compin, ccp->uncompin); 152 153 prompt_Printf(arg->prompt, "\n Defaults: "); 154 prompt_Printf(arg->prompt, "FSM retry = %us, max %u Config" 155 " REQ%s, %u Term REQ%s\n", ccp->cfg.fsm.timeout, 156 ccp->cfg.fsm.maxreq, ccp->cfg.fsm.maxreq == 1 ? "" : "s", 157 ccp->cfg.fsm.maxtrm, ccp->cfg.fsm.maxtrm == 1 ? "" : "s"); 158 prompt_Printf(arg->prompt, " deflate windows: "); 159 prompt_Printf(arg->prompt, "incoming = %d, ", ccp->cfg.deflate.in.winsize); 160 prompt_Printf(arg->prompt, "outgoing = %d\n", ccp->cfg.deflate.out.winsize); 161 prompt_Printf(arg->prompt, " DEFLATE: %s\n", 162 command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE])); 163 prompt_Printf(arg->prompt, " PREDICTOR1: %s\n", 164 command_ShowNegval(ccp->cfg.neg[CCP_NEG_PRED1])); 165 prompt_Printf(arg->prompt, " DEFLATE24: %s\n", 166 command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE24])); 167 return 0; 168} 169 170void 171ccp_SetupCallbacks(struct ccp *ccp) 172{ 173 ccp->fsm.fn = &ccp_Callbacks; 174 ccp->fsm.FsmTimer.name = ccp_TimerNames[0]; 175 ccp->fsm.OpenTimer.name = ccp_TimerNames[1]; 176 ccp->fsm.StoppedTimer.name = ccp_TimerNames[2]; 177} 178 179void 180ccp_Init(struct ccp *ccp, struct bundle *bundle, struct link *l, 181 const struct fsm_parent *parent) 182{ 183 /* Initialise ourselves */ 184 185 fsm_Init(&ccp->fsm, "CCP", PROTO_CCP, 1, CCP_MAXCODE, LogCCP, 186 bundle, l, parent, &ccp_Callbacks, ccp_TimerNames); 187 188 ccp->cfg.deflate.in.winsize = 0; 189 ccp->cfg.deflate.out.winsize = 15; 190 ccp->cfg.fsm.timeout = DEF_FSMRETRY; 191 ccp->cfg.fsm.maxreq = DEF_FSMTRIES; 192 ccp->cfg.fsm.maxtrm = DEF_FSMTRIES; 193 ccp->cfg.neg[CCP_NEG_DEFLATE] = NEG_ENABLED|NEG_ACCEPTED; 194 ccp->cfg.neg[CCP_NEG_PRED1] = NEG_ENABLED|NEG_ACCEPTED; 195 ccp->cfg.neg[CCP_NEG_DEFLATE24] = 0; 196 197 ccp_Setup(ccp); 198} 199 200void 201ccp_Setup(struct ccp *ccp) 202{ 203 /* Set ourselves up for a startup */ 204 ccp->fsm.open_mode = 0; 205 ccp->his_proto = ccp->my_proto = -1; 206 ccp->reset_sent = ccp->last_reset = -1; 207 ccp->in.algorithm = ccp->out.algorithm = -1; 208 ccp->in.state = ccp->out.state = NULL; 209 ccp->in.opt.id = -1; 210 ccp->out.opt = NULL; 211 ccp->his_reject = ccp->my_reject = 0; 212 ccp->uncompout = ccp->compout = 0; 213 ccp->uncompin = ccp->compin = 0; 214} 215 216static void 217CcpInitRestartCounter(struct fsm *fp, int what) 218{ 219 /* Set fsm timer load */ 220 struct ccp *ccp = fsm2ccp(fp); 221 222 fp->FsmTimer.load = ccp->cfg.fsm.timeout * SECTICKS; 223 switch (what) { 224 case FSM_REQ_TIMER: 225 fp->restart = ccp->cfg.fsm.maxreq; 226 break; 227 case FSM_TRM_TIMER: 228 fp->restart = ccp->cfg.fsm.maxtrm; 229 break; 230 default: 231 fp->restart = 1; 232 break; 233 } 234} 235 236static void 237CcpSendConfigReq(struct fsm *fp) 238{ 239 /* Send config REQ please */ 240 struct ccp *ccp = fsm2ccp(fp); 241 struct ccp_opt **o; 242 u_char *cp, buff[100]; 243 int f, alloc; 244 245 cp = buff; 246 o = &ccp->out.opt; 247 alloc = ccp->his_reject == 0 && ccp->out.opt == NULL; 248 ccp->my_proto = -1; 249 ccp->out.algorithm = -1; 250 for (f = 0; f < NALGORITHMS; f++) 251 if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) && 252 !REJECTED(ccp, algorithm[f]->id)) { 253 254 if (!alloc) 255 for (o = &ccp->out.opt; *o != NULL; o = &(*o)->next) 256 if ((*o)->val.id == algorithm[f]->id && (*o)->algorithm == f) 257 break; 258 259 if (alloc || *o == NULL) { 260 *o = (struct ccp_opt *)malloc(sizeof(struct ccp_opt)); 261 (*o)->val.id = algorithm[f]->id; 262 (*o)->val.len = 2; 263 (*o)->next = NULL; 264 (*o)->algorithm = f; 265 (*algorithm[f]->o.OptInit)(&(*o)->val, &ccp->cfg); 266 } 267 268 if (cp + (*o)->val.len > buff + sizeof buff) { 269 log_Printf(LogERROR, "%s: CCP REQ buffer overrun !\n", fp->link->name); 270 break; 271 } 272 memcpy(cp, &(*o)->val, (*o)->val.len); 273 cp += (*o)->val.len; 274 275 ccp->my_proto = (*o)->val.id; 276 ccp->out.algorithm = f; 277 278 if (alloc) 279 o = &(*o)->next; 280 } 281 282 fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, cp - buff); 283} 284 285void 286ccp_SendResetReq(struct fsm *fp) 287{ 288 /* We can't read our input - ask peer to reset */ 289 struct ccp *ccp = fsm2ccp(fp); 290 291 ccp->reset_sent = fp->reqid; 292 ccp->last_reset = -1; 293 fsm_Output(fp, CODE_RESETREQ, fp->reqid, NULL, 0); 294} 295 296static void 297CcpSentTerminateReq(struct fsm *fp) 298{ 299 /* Term REQ just sent by FSM */ 300} 301 302static void 303CcpSendTerminateAck(struct fsm *fp, u_char id) 304{ 305 /* Send Term ACK please */ 306 fsm_Output(fp, CODE_TERMACK, id, NULL, 0); 307} 308 309static void 310CcpRecvResetReq(struct fsm *fp) 311{ 312 /* Got a reset REQ, reset outgoing dictionary */ 313 struct ccp *ccp = fsm2ccp(fp); 314 if (ccp->out.state != NULL) 315 (*algorithm[ccp->out.algorithm]->o.Reset)(ccp->out.state); 316} 317 318static void 319CcpLayerStart(struct fsm *fp) 320{ 321 /* We're about to start up ! */ 322 struct ccp *ccp = fsm2ccp(fp); 323 324 log_Printf(LogCCP, "%s: LayerStart.\n", fp->link->name); 325 fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3; 326} 327 328static void 329CcpLayerDown(struct fsm *fp) 330{ 331 /* About to come down */ 332 struct ccp *ccp = fsm2ccp(fp); 333 struct ccp_opt *next; 334 335 log_Printf(LogCCP, "%s: LayerDown.\n", fp->link->name); 336 if (ccp->in.state != NULL) { 337 (*algorithm[ccp->in.algorithm]->i.Term)(ccp->in.state); 338 ccp->in.state = NULL; 339 ccp->in.algorithm = -1; 340 } 341 if (ccp->out.state != NULL) { 342 (*algorithm[ccp->out.algorithm]->o.Term)(ccp->out.state); 343 ccp->out.state = NULL; 344 ccp->out.algorithm = -1; 345 } 346 ccp->his_reject = ccp->my_reject = 0; 347 348 while (ccp->out.opt) { 349 next = ccp->out.opt->next; 350 free(ccp->out.opt); 351 ccp->out.opt = next; 352 } 353 ccp_Setup(ccp); 354} 355 356static void 357CcpLayerFinish(struct fsm *fp) 358{ 359 /* We're now down */ 360 log_Printf(LogCCP, "%s: LayerFinish.\n", fp->link->name); 361} 362 363/* 364 * Called when CCP has reached the OPEN state 365 */ 366static int 367CcpLayerUp(struct fsm *fp) 368{ 369 /* We're now up */ 370 struct ccp *ccp = fsm2ccp(fp); 371 372 log_Printf(LogCCP, "%s: LayerUp.\n", fp->link->name); 373 374 if (ccp->in.state == NULL && ccp->in.algorithm >= 0 && 375 ccp->in.algorithm < NALGORITHMS) { 376 ccp->in.state = (*algorithm[ccp->in.algorithm]->i.Init)(&ccp->in.opt); 377 if (ccp->in.state == NULL) { 378 log_Printf(LogERROR, "%s: %s (in) initialisation failure\n", 379 fp->link->name, protoname(ccp->his_proto)); 380 ccp->his_proto = ccp->my_proto = -1; 381 fsm_Close(fp); 382 return 0; 383 } 384 } 385 386 if (ccp->out.state == NULL && ccp->out.algorithm >= 0 && 387 ccp->out.algorithm < NALGORITHMS) { 388 ccp->out.state = (*algorithm[ccp->out.algorithm]->o.Init) 389 (&ccp->out.opt->val); 390 if (ccp->out.state == NULL) { 391 log_Printf(LogERROR, "%s: %s (out) initialisation failure\n", 392 fp->link->name, protoname(ccp->my_proto)); 393 ccp->his_proto = ccp->my_proto = -1; 394 fsm_Close(fp); 395 return 0; 396 } 397 } 398 399 fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3; 400 401 log_Printf(LogCCP, "%s: Out = %s[%d], In = %s[%d]\n", 402 fp->link->name, protoname(ccp->my_proto), ccp->my_proto, 403 protoname(ccp->his_proto), ccp->his_proto); 404 405 return 1; 406} 407 408static void 409CcpDecodeConfig(struct fsm *fp, u_char *cp, int plen, int mode_type, 410 struct fsm_decode *dec) 411{ 412 /* Deal with incoming data */ 413 struct ccp *ccp = fsm2ccp(fp); 414 int type, length; 415 int f; 416 const char *end; 417 418 while (plen >= sizeof(struct fsmconfig)) { 419 type = *cp; 420 length = cp[1]; 421 422 if (length == 0) { 423 log_Printf(LogCCP, "%s: CCP size zero\n", fp->link->name); 424 break; 425 } 426 427 if (length > sizeof(struct lcp_opt)) { 428 length = sizeof(struct lcp_opt); 429 log_Printf(LogCCP, "%s: Warning: Truncating length to %d\n", 430 fp->link->name, length); 431 } 432 433 for (f = NALGORITHMS-1; f > -1; f--) 434 if (algorithm[f]->id == type) 435 break; 436 437 end = f == -1 ? "" : (*algorithm[f]->Disp)((struct lcp_opt *)cp); 438 if (end == NULL) 439 end = ""; 440 441 if (type < NCFTYPES) 442 log_Printf(LogCCP, " %s[%d] %s\n", cftypes[type], length, end); 443 else 444 log_Printf(LogCCP, " ???[%d] %s\n", length, end); 445 446 if (f == -1) { 447 /* Don't understand that :-( */ 448 if (mode_type == MODE_REQ) { 449 ccp->my_reject |= (1 << type); 450 memcpy(dec->rejend, cp, length); 451 dec->rejend += length; 452 } 453 } else { 454 struct ccp_opt *o; 455 456 switch (mode_type) { 457 case MODE_REQ: 458 if (IsAccepted(ccp->cfg.neg[algorithm[f]->Neg]) && 459 ccp->in.algorithm == -1) { 460 memcpy(&ccp->in.opt, cp, length); 461 switch ((*algorithm[f]->i.Set)(&ccp->in.opt, &ccp->cfg)) { 462 case MODE_REJ: 463 memcpy(dec->rejend, &ccp->in.opt, ccp->in.opt.len); 464 dec->rejend += ccp->in.opt.len; 465 break; 466 case MODE_NAK: 467 memcpy(dec->nakend, &ccp->in.opt, ccp->in.opt.len); 468 dec->nakend += ccp->in.opt.len; 469 break; 470 case MODE_ACK: 471 memcpy(dec->ackend, cp, length); 472 dec->ackend += length; 473 ccp->his_proto = type; 474 ccp->in.algorithm = f; /* This one'll do :-) */ 475 break; 476 } 477 } else { 478 memcpy(dec->rejend, cp, length); 479 dec->rejend += length; 480 } 481 break; 482 case MODE_NAK: 483 for (o = ccp->out.opt; o != NULL; o = o->next) 484 if (o->val.id == cp[0]) 485 break; 486 if (o == NULL) 487 log_Printf(LogCCP, "%s: Warning: Ignoring peer NAK of unsent option\n", 488 fp->link->name); 489 else { 490 memcpy(&o->val, cp, length); 491 if ((*algorithm[f]->o.Set)(&o->val) == MODE_ACK) 492 ccp->my_proto = algorithm[f]->id; 493 else { 494 ccp->his_reject |= (1 << type); 495 ccp->my_proto = -1; 496 } 497 } 498 break; 499 case MODE_REJ: 500 ccp->his_reject |= (1 << type); 501 ccp->my_proto = -1; 502 break; 503 } 504 } 505 506 plen -= cp[1]; 507 cp += cp[1]; 508 } 509 510 if (mode_type != MODE_NOP) { 511 if (dec->rejend != dec->rej) { 512 /* rejects are preferred */ 513 dec->ackend = dec->ack; 514 dec->nakend = dec->nak; 515 if (ccp->in.state == NULL) { 516 ccp->his_proto = -1; 517 ccp->in.algorithm = -1; 518 } 519 } else if (dec->nakend != dec->nak) { 520 /* then NAKs */ 521 dec->ackend = dec->ack; 522 if (ccp->in.state == NULL) { 523 ccp->his_proto = -1; 524 ccp->in.algorithm = -1; 525 } 526 } 527 } 528} 529 530void 531ccp_Input(struct ccp *ccp, struct bundle *bundle, struct mbuf *bp) 532{ 533 /* Got PROTO_CCP from link */ 534 if (bundle_Phase(bundle) == PHASE_NETWORK) 535 fsm_Input(&ccp->fsm, bp); 536 else { 537 if (bundle_Phase(bundle) < PHASE_NETWORK) 538 log_Printf(LogCCP, "%s: Error: Unexpected CCP in phase %s (ignored)\n", 539 ccp->fsm.link->name, bundle_PhaseName(bundle)); 540 mbuf_Free(bp); 541 } 542} 543 544static void 545CcpRecvResetAck(struct fsm *fp, u_char id) 546{ 547 /* Got a reset ACK, reset incoming dictionary */ 548 struct ccp *ccp = fsm2ccp(fp); 549 550 if (ccp->reset_sent != -1) { 551 if (id != ccp->reset_sent) { 552 log_Printf(LogCCP, "%s: Incorrect ResetAck (id %d, not %d)" 553 " ignored\n", fp->link->name, id, ccp->reset_sent); 554 return; 555 } 556 /* Whaddaya know - a correct reset ack */ 557 } else if (id == ccp->last_reset) 558 log_Printf(LogCCP, "%s: Duplicate ResetAck (resetting again)\n", 559 fp->link->name); 560 else { 561 log_Printf(LogCCP, "%s: Unexpected ResetAck (id %d) ignored\n", 562 fp->link->name, id); 563 return; 564 } 565 566 ccp->last_reset = ccp->reset_sent; 567 ccp->reset_sent = -1; 568 if (ccp->in.state != NULL) 569 (*algorithm[ccp->in.algorithm]->i.Reset)(ccp->in.state); 570} 571 572int 573ccp_Compress(struct ccp *ccp, struct link *l, int pri, u_short proto, 574 struct mbuf *m) 575{ 576 /* 577 * Compress outgoing data. It's already deemed to be suitable Network 578 * Layer data. 579 */ 580 if (ccp->fsm.state == ST_OPENED && ccp->out.state != NULL) 581 return (*algorithm[ccp->out.algorithm]->o.Write) 582 (ccp->out.state, ccp, l, pri, proto, m); 583 return 0; 584} 585 586struct mbuf * 587ccp_Decompress(struct ccp *ccp, u_short *proto, struct mbuf *bp) 588{ 589 /* 590 * If proto isn't PROTO_[I]COMPD, we still want to pass it to the 591 * decompression routines so that the dictionary's updated 592 */ 593 if (ccp->fsm.state == ST_OPENED) { 594 if (*proto == PROTO_COMPD || *proto == PROTO_ICOMPD) { 595 /* Decompress incoming data */ 596 if (ccp->reset_sent != -1) 597 /* Send another REQ and put the packet in the bit bucket */ 598 fsm_Output(&ccp->fsm, CODE_RESETREQ, ccp->reset_sent, NULL, 0); 599 else if (ccp->in.state != NULL) 600 return (*algorithm[ccp->in.algorithm]->i.Read) 601 (ccp->in.state, ccp, proto, bp); 602 mbuf_Free(bp); 603 bp = NULL; 604 } else if (PROTO_COMPRESSIBLE(*proto) && ccp->in.state != NULL) 605 /* Add incoming Network Layer traffic to our dictionary */ 606 (*algorithm[ccp->in.algorithm]->i.DictSetup) 607 (ccp->in.state, ccp, *proto, bp); 608 } 609 610 return bp; 611} 612 613u_short 614ccp_Proto(struct ccp *ccp) 615{ 616 return !link2physical(ccp->fsm.link) || !ccp->fsm.bundle->ncp.mp.active ? 617 PROTO_COMPD : PROTO_ICOMPD; 618} 619 620int 621ccp_SetOpenMode(struct ccp *ccp) 622{ 623 int f; 624 625 for (f = 0; f < CCP_NEG_TOTAL; f++) 626 if (IsEnabled(ccp->cfg.neg[f])) { 627 ccp->fsm.open_mode = 0; 628 return 1; 629 } 630 631 ccp->fsm.open_mode = OPEN_PASSIVE; /* Go straight to ST_STOPPED ? */ 632 633 for (f = 0; f < CCP_NEG_TOTAL; f++) 634 if (IsAccepted(ccp->cfg.neg[f])) 635 return 1; 636 637 return 0; /* No CCP at all */ 638} 639