Variant ZendPack::pack()

in hphp/runtime/base/zend-pack.cpp [160:530]


Variant ZendPack::pack(const String& fmt, const Array& argv) {
  /* Preprocess format into formatcodes and formatargs */
  req::TinyVector<char, 64> formatcodes; // up to 64 codes on the stack
  req::TinyVector<int, 64> formatargs;
  int argc = argv.size();

  const char *format = fmt.c_str();
  int formatlen = fmt.size();
  int currentarg = 0;
  for (int i = 0; i < formatlen; ) {
    char code = format[i++];
    int arg = 1;

    /* Handle format arguments if any */
    if (i < formatlen) {
      char c = format[i];

      if (c == '*') {
        arg = -1;
        i++;
      }
      else if (c >= '0' && c <= '9') {
        arg = atoi(&format[i]);

        while (format[i] >= '0' && format[i] <= '9' && i < formatlen) {
          i++;
        }
      }
    }

    /* Handle special arg '*' for all codes and check argv overflows */
    switch ((int) code) {
      /* Never uses any args */
    case 'x':
    case 'X':
    case '@':
      if (arg < 0) {
        raise_invalid_argument_warning("Type %c: '*' ignored", code);
        arg = 1;
      }
      break;

      /* Always uses one arg */
    case 'a':
    case 'A':
    case 'h':
    case 'H':
    case 'Z':
      if (currentarg >= argc) {
        raise_invalid_argument_warning("Type %c: not enough arguments", code);
        return false;
      }

      if (arg < 0) {
        arg = argv[currentarg].toString().size();
        //add one, because Z is always NUL-terminated
        if (code == 'Z') {
          arg++;
        }
      }

      currentarg++;
      break;

      /* Use as many args as specified */
    case 'q':
    case 'Q':
    case 'J':
    case 'P':
    case 'c':
    case 'C':
    case 's':
    case 'S':
    case 'i':
    case 'I':
    case 'l':
    case 'L':
    case 'n':
    case 'N':
    case 'v':
    case 'V':
    case 'f':
    case 'd':
      if (arg < 0) {
        arg = argc - currentarg;
      }

      currentarg += arg;

      if (currentarg > argc) {
        raise_invalid_argument_warning("Type %c: too few arguments", code);
        return false;
      }
      break;

    default:
      raise_invalid_argument_warning("Type %c: unknown format code", code);
      return false;
    }

    formatcodes.push_back(code);
    formatargs.push_back(arg);
  }

  if (currentarg < argc) {
    raise_invalid_argument_warning("%d arguments unused", (argc - currentarg));
  }

  int outputpos = 0, outputsize = 0;
  /* Calculate output length and upper bound while processing*/
  for (int i = 0; i < (int)formatcodes.size(); i++) {
    int code = (int) formatcodes[i];
    int arg = formatargs[i];

    switch ((int) code) {
    case 'h':
    case 'H':
      INC_OUTPUTPOS((arg + (arg % 2)) / 2,1);  /* 4 bit per arg */
      break;

    case 'a':
    case 'A':
    case 'c':
    case 'C':
    case 'x':
    case 'Z':
      INC_OUTPUTPOS(arg,1);    /* 8 bit per arg */
      break;

    case 's':
    case 'S':
    case 'n':
    case 'v':
      INC_OUTPUTPOS(arg,2);    /* 16 bit per arg */
      break;

    case 'i':
    case 'I':
      INC_OUTPUTPOS(arg,sizeof(int));
      break;

    case 'l':
    case 'L':
    case 'N':
    case 'V':
      INC_OUTPUTPOS(arg,4);    /* 32 bit per arg */
      break;
    case 'q':
    case 'Q':
    case 'J':
    case 'P':
      INC_OUTPUTPOS(arg,8);    /* 64 bit per arg */
      break;

    case 'f':
      INC_OUTPUTPOS(arg,sizeof(float));
      break;

    case 'd':
      INC_OUTPUTPOS(arg,sizeof(double));
      break;

    case 'X':
      outputpos -= arg;

      if (outputpos < 0) {
        raise_invalid_argument_warning("Type %c: outside of string", code);
        outputpos = 0;
      }
      break;

    case '@':
      outputpos = arg;
      break;
    }

    if (outputsize < outputpos) {
      outputsize = outputpos;
    }
  }

  String str = String(outputsize, ReserveString);
  char *output = str.mutableData();
  outputpos = 0;
  currentarg = 0;

  /* Do actual packing */
  for (int i = 0; i < (int)formatcodes.size(); i++) {
    int code = (int) formatcodes[i];
    int arg = formatargs[i];
    String val;
    const char *s;
    int slen;

    switch ((int) code) {
    case 'a':
    case 'A':
    case 'Z': {
      int arg_cp = (code != 'Z') ? arg : std::max(0, arg - 1);
      memset(&output[outputpos], (code != 'A') ? '\0' : ' ', arg);
      val = argv[currentarg++].toString();
      s = val.c_str();
      slen = val.size();
      memcpy(&output[outputpos], s, (slen < arg_cp) ? slen : arg_cp);
      outputpos += arg;
    }
    break;

    case 'h':
    case 'H': {
      int nibbleshift = (code == 'h') ? 0 : 4;
      int first = 1;
      const char *v;

      val = argv[currentarg++].toString();
      v = val.data();
      slen = val.size();
      outputpos--;
      if (arg > slen) {
        raise_invalid_argument_warning
          ("Type %c: not enough characters in string", code);
        arg = slen;
      }

      while (arg-- > 0) {
        char n = *v++;

        if (n >= '0' && n <= '9') {
          n -= '0';
        } else if (n >= 'A' && n <= 'F') {
          n -= ('A' - 10);
        } else if (n >= 'a' && n <= 'f') {
          n -= ('a' - 10);
        } else {
          raise_invalid_argument_warning("Type %c: illegal hex digit %c", code, n);
          n = 0;
        }

        if (first--) {
          output[++outputpos] = 0;
        } else {
          first = 1;
        }

        output[outputpos] |= (n << nibbleshift);
        nibbleshift = (nibbleshift + 4) & 7;
      }

      outputpos++;
      break;
    }

    case 'c':
    case 'C':
      while (arg-- > 0) {
        pack(argv[currentarg++], 1, byte_map, &output[outputpos]);
        outputpos++;
      }
      break;

    case 's':
    case 'S':
    case 'n':
    case 'v': {
      int64_t *map = machine_endian_short_map;

      if (code == 'n') {
        map = big_endian_short_map;
      } else if (code == 'v') {
        map = little_endian_short_map;
      }

      while (arg-- > 0) {
        pack(argv[currentarg++], 2, map, &output[outputpos]);
        outputpos += 2;
      }
      break;
    }

    case 'i':
    case 'I':
      while (arg-- > 0) {
        pack(argv[currentarg++], sizeof(int), int_map, &output[outputpos]);
        outputpos += sizeof(int);
      }
      break;

    case 'l':
    case 'L':
    case 'N':
    case 'V': {
      int64_t *map = machine_endian_int32_map;

      if (code == 'N') {
        map = big_endian_int32_map;
      } else if (code == 'V') {
        map = little_endian_int32_map;
      }

      while (arg-- > 0) {
        pack(argv[currentarg++], 4, map, &output[outputpos]);
        outputpos += 4;
      }
      break;
    }

    case 'q':
    case 'Q':
    case 'J':
    case 'P': {
      int64_t *map = machine_endian_int64_map;
      if (code == 'J') {
        map = big_endian_int64_map;
      } else if (code == 'P') {
        map = little_endian_int64_map;
      }

      while (arg-- > 0) {
        pack(argv[currentarg++], 8, map, &output[outputpos]);
        outputpos += 8;
      }
      break;
    }

    case 'f': {
      float v;

      while (arg-- > 0) {
        v = argv[currentarg++].toDouble();
        memcpy(&output[outputpos], &v, sizeof(v));
        outputpos += sizeof(v);
      }
      break;
    }

    case 'd': {
      double v;

      while (arg-- > 0) {
        v = argv[currentarg++].toDouble();
        memcpy(&output[outputpos], &v, sizeof(v));
        outputpos += sizeof(v);
      }
      break;
    }

    case 'x':
      memset(&output[outputpos], '\0', arg);
      outputpos += arg;
      break;

    case 'X':
      outputpos -= arg;

      if (outputpos < 0) {
        outputpos = 0;
      }
      break;

    case '@':
      if (arg > outputpos) {
        memset(&output[outputpos], '\0', arg - outputpos);
      }
      outputpos = arg;
      break;
    }
  }

  str.setSize(outputpos);
  return str;
}