@Override
 public OutputStream getOutputStream() throws IOException {
   synchronized (this) {
     if (readOnly) throw new IOException("Read only");
   }
   OutputStream os = underlying.getOutputStream();
   return AEADOutputStream.createAES(os, key, NodeStarter.getGlobalSecureRandom());
 }
  /**
   * You have to synchronize on this <code>WoTMessageListInserter</code> and then on the <code>
   * WoTMessageManager</code> when using this function.
   */
  private void insertMessageList(WoTOwnMessageList list)
      throws TransformerException, ParserConfigurationException, NoSuchMessageException,
          IOException, InsertException {
    Bucket tempB = mTBF.makeBucket(4096); /* TODO: set to a reasonable value */
    OutputStream os = null;

    try {
      os = tempB.getOutputStream();
      // This is what requires synchronization on the WoTMessageManager: While being marked as
      // "being inserted", message lists cannot be modified anymore,
      // so it must be guranteed that the "being inserted" mark does not change while we encode the
      // XML etc.
      mMessageManager.onMessageListInsertStarted(list);

      mXML.encode(mMessageManager, list, os);
      os.close();
      os = null;
      tempB.setReadOnly();

      /* We do not specifiy a ClientMetaData with mimetype because that would result in the insertion of an additional CHK */
      InsertBlock ib = new InsertBlock(tempB, null, list.getInsertURI());
      InsertContext ictx = mClient.getInsertContext(true);

      ClientPutter pu =
          mClient.insert(
              ib, false, null, false, ictx, this, RequestStarter.INTERACTIVE_PRIORITY_CLASS);
      addInsert(pu);
      tempB = null;

      if (logDEBUG)
        Logger.debug(this, "Started insert of WoTOwnMessageList at request URI " + list.getURI());
    } finally {
      if (tempB != null) tempB.free();
      Closer.close(os);
    }
  }
  private void handleZIPArchive(
      ArchiveStoreContext ctx,
      FreenetURI key,
      InputStream data,
      String element,
      ArchiveExtractCallback callback,
      MutableBoolean gotElement,
      boolean throwAtExit,
      ClientContext context)
      throws ArchiveFailureException, ArchiveRestartException {
    if (logMINOR) Logger.minor(this, "Handling a ZIP Archive");
    ZipInputStream zis = null;
    try {
      zis = new ZipInputStream(data);

      // MINOR: Assumes the first entry in the zip is a directory.
      ZipEntry entry;

      byte[] buf = new byte[32768];
      HashSet<String> names = new HashSet<String>();
      boolean gotMetadata = false;

      outerZIP:
      while (true) {
        entry = zis.getNextEntry();
        if (entry == null) break;
        if (entry.isDirectory()) continue;
        String name = stripLeadingSlashes(entry.getName());
        if (names.contains(name)) {
          Logger.error(this, "Duplicate key " + name + " in archive " + key);
          continue;
        }
        long size = entry.getSize();
        if (name.equals(".metadata")) gotMetadata = true;
        if (size > maxArchivedFileSize && !name.equals(element)) {
          addErrorElement(
              ctx,
              key,
              name,
              "File too big: "
                  + maxArchivedFileSize
                  + " greater than current archived file size limit "
                  + maxArchivedFileSize,
              true);
        } else {
          // Read the element
          long realLen = 0;
          Bucket output = tempBucketFactory.makeBucket(size);
          OutputStream out = output.getOutputStream();
          try {

            int readBytes;
            while ((readBytes = zis.read(buf)) > 0) {
              out.write(buf, 0, readBytes);
              readBytes += realLen;
              if (readBytes > maxArchivedFileSize) {
                addErrorElement(
                    ctx,
                    key,
                    name,
                    "File too big: "
                        + maxArchivedFileSize
                        + " greater than current archived file size limit "
                        + maxArchivedFileSize,
                    true);
                out.close();
                out = null;
                output.free();
                continue outerZIP;
              }
            }

          } finally {
            if (out != null) out.close();
          }
          if (size <= maxArchivedFileSize) {
            addStoreElement(ctx, key, name, output, gotElement, element, callback, context);
            names.add(name);
            trimStoredData();
          } else {
            // We are here because they asked for this file.
            callback.gotBucket(output, context);
            gotElement.value = true;
            addErrorElement(
                ctx,
                key,
                name,
                "File too big: "
                    + size
                    + " greater than current archived file size limit "
                    + maxArchivedFileSize,
                true);
          }
        }
      }

      // If no metadata, generate some
      if (!gotMetadata) {
        generateMetadata(ctx, key, names, gotElement, element, callback, context);
        trimStoredData();
      }
      if (throwAtExit) throw new ArchiveRestartException("Archive changed on re-fetch");

      if ((!gotElement.value) && element != null) callback.notInArchive(context);

    } catch (IOException e) {
      throw new ArchiveFailureException("Error reading archive: " + e.getMessage(), e);
    } finally {
      if (zis != null) {
        try {
          zis.close();
        } catch (IOException e) {
          Logger.error(this, "Failed to close stream: " + e, e);
        }
      }
    }
  }
    public void parseSubIndex() throws TaskAbortException {
      synchronized (parsingSubindex) {
        // Transfer all requests waiting on this subindex to the parsing list
        synchronized (waitingOnSubindex) {
          parsingSubindex.addAll(waitingOnSubindex);
          waitingOnSubindex.removeAll(parsingSubindex);
        }
        // Set status of all those about to be parsed to PARSE
        for (FindRequest r : parsingSubindex) r.setStage(FindRequest.Stages.PARSE);

        // Multi-stage parse to minimise memory usage.

        // Stage 1: Extract the declaration (first tag), copy everything before "<files " to one
        // bucket, plus everything after "</files>".
        // Copy the declaration, plus everything between the two (inclusive) to another bucket.

        Bucket mainBucket, filesBucket;

        try {
          InputStream is = bucket.getInputStream();
          mainBucket = pr.getNode().clientCore.tempBucketFactory.makeBucket(-1);
          filesBucket = pr.getNode().clientCore.tempBucketFactory.makeBucket(-1);
          OutputStream mainOS = new BufferedOutputStream(mainBucket.getOutputStream());
          OutputStream filesOS = new BufferedOutputStream(filesBucket.getOutputStream());
          // OutputStream mainOS = new BufferedOutputStream(new FileOutputStream("main.tmp"));
          // OutputStream filesOS = new BufferedOutputStream(new FileOutputStream("files.tmp"));

          BufferedInputStream bis = new BufferedInputStream(is);

          byte greaterThan = ">".getBytes("UTF-8")[0];
          byte[] filesPrefix = "<files ".getBytes("UTF-8");
          byte[] filesPrefixAlt = "<files>".getBytes("UTF-8");
          assert (filesPrefix.length == filesPrefixAlt.length);
          byte[] filesEnd = "</files>".getBytes("UTF-8");

          final int MODE_SEEKING_DECLARATION = 1;
          final int MODE_SEEKING_FILES = 2;
          final int MODE_COPYING_FILES = 3;
          final int MODE_COPYING_REST = 4;
          int mode = MODE_SEEKING_DECLARATION;
          int b;
          byte[] declarationBuf = new byte[100];
          int declarationPtr = 0;
          byte[] prefixBuffer = new byte[filesPrefix.length];
          int prefixPtr = 0;
          byte[] endBuffer = new byte[filesEnd.length];
          int endPtr = 0;
          while ((b = bis.read()) != -1) {
            if (mode == MODE_SEEKING_DECLARATION) {
              if (declarationPtr == declarationBuf.length)
                throw new TaskAbortException("Could not split up XML: declaration too long", null);
              declarationBuf[declarationPtr++] = (byte) b;
              mainOS.write(b);
              filesOS.write(b);
              if (b == greaterThan) {
                mode = MODE_SEEKING_FILES;
              }
            } else if (mode == MODE_SEEKING_FILES) {
              if (prefixPtr != prefixBuffer.length) {
                prefixBuffer[prefixPtr++] = (byte) b;
              } else {
                if (Fields.byteArrayEqual(filesPrefix, prefixBuffer)
                    || Fields.byteArrayEqual(filesPrefixAlt, prefixBuffer)) {
                  mode = MODE_COPYING_FILES;
                  filesOS.write(prefixBuffer);
                  filesOS.write(b);
                } else {
                  mainOS.write(prefixBuffer[0]);
                  System.arraycopy(prefixBuffer, 1, prefixBuffer, 0, prefixBuffer.length - 1);
                  prefixBuffer[prefixBuffer.length - 1] = (byte) b;
                }
              }
            } else if (mode == MODE_COPYING_FILES) {
              if (endPtr != endBuffer.length) {
                endBuffer[endPtr++] = (byte) b;
              } else {
                if (Fields.byteArrayEqual(filesEnd, endBuffer)) {
                  mode = MODE_COPYING_REST;
                  filesOS.write(endBuffer);
                  mainOS.write(b);
                } else {
                  filesOS.write(endBuffer[0]);
                  System.arraycopy(endBuffer, 1, endBuffer, 0, endBuffer.length - 1);
                  endBuffer[endBuffer.length - 1] = (byte) b;
                }
              }
            } else if (mode == MODE_COPYING_REST) {
              mainOS.write(b);
            }
          }

          if (mode != MODE_COPYING_REST)
            throw new TaskAbortException("Could not split up XML: Last mode was " + mode, null);

          mainOS.close();
          filesOS.close();
        } catch (IOException e) {
          throw new TaskAbortException("Could not split XML: ", e);
        }

        if (logMINOR) Logger.minor(this, "Finished splitting XML");

        try {

          SAXParserFactory factory = SAXParserFactory.newInstance();
          factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
          SAXParser saxParser = factory.newSAXParser();

          // Stage 2: Parse the first bucket, find the keyword we want, find the file id's.

          InputStream is = mainBucket.getInputStream();
          StageTwoHandler stageTwoHandler = new StageTwoHandler();
          saxParser.parse(is, stageTwoHandler);
          if (logMINOR) Logger.minor(this, "Finished stage two XML parse");
          is.close();

          // Stage 3: Parse the second bucket, extract the <file>'s for the specific ID's.

          is = filesBucket.getInputStream();
          StageThreeHandler stageThreeHandler = new StageThreeHandler();
          saxParser.parse(is, stageThreeHandler);
          if (logMINOR) Logger.minor(this, "Finished stage three XML parse");
          is.close();

          Logger.minor(this, "parsing finished " + parsingSubindex.toString());
          for (FindRequest findRequest : parsingSubindex) {
            findRequest.setFinished();
          }
          parsingSubindex.clear();
        } catch (Exception err) {
          Logger.error(this, "Error parsing " + filename, err);
          throw new TaskAbortException("Could not parse XML: ", err);
        }
      }
    }
  public static void main(String[] args)
      throws InvalidThresholdException, IOException, NodeInitException, InterruptedException {
    Node node = null;
    Node secondNode = null;
    try {
      String ipOverride = null;
      if (args.length > 0) ipOverride = args[0];
      File dir = new File("bootstrap-push-pull-test");
      FileUtil.removeAll(dir);
      RandomSource random =
          NodeStarter.globalTestInit(dir.getPath(), false, LogLevel.ERROR, "", false);
      File seednodes = new File("seednodes.fref");
      if (!seednodes.exists() || seednodes.length() == 0 || !seednodes.canRead()) {
        System.err.println("Unable to read seednodes.fref, it doesn't exist, or is empty");
        System.exit(EXIT_NO_SEEDNODES);
      }
      File innerDir = new File(dir, Integer.toString(DARKNET_PORT1));
      innerDir.mkdir();
      FileInputStream fis = new FileInputStream(seednodes);
      FileUtil.writeTo(fis, new File(innerDir, "seednodes.fref"));
      fis.close();
      // Create one node
      Executor executor = new PooledExecutor();
      node =
          NodeStarter.createTestNode(
              DARKNET_PORT1,
              OPENNET_PORT1,
              dir.getPath(),
              false,
              Node.DEFAULT_MAX_HTL,
              0,
              random,
              executor,
              1000,
              5 * 1024 * 1024,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              12 * 1024,
              false,
              true,
              false,
              false,
              ipOverride);
      // NodeCrypto.DISABLE_GROUP_STRIP = true;
      // Logger.setupStdoutLogging(LogLevel.MINOR,
      // "freenet:NORMAL,freenet.node.NodeDispatcher:MINOR,freenet.node.FNPPacketMangler:MINOR");
      Logger.getChain().setThreshold(LogLevel.ERROR); // kill logging
      // Start it
      node.start(true);
      if (!TestUtil.waitForNodes(node)) {
        node.park();
        System.exit(EXIT_FAILED_TARGET);
      }
      System.err.println("Creating test data: " + TEST_SIZE + " bytes.");
      Bucket data = node.clientCore.tempBucketFactory.makeBucket(TEST_SIZE);
      OutputStream os = data.getOutputStream();
      byte[] buf = new byte[4096];
      for (long written = 0; written < TEST_SIZE; ) {
        node.fastWeakRandom.nextBytes(buf);
        int toWrite = (int) Math.min(TEST_SIZE - written, buf.length);
        os.write(buf, 0, toWrite);
        written += toWrite;
      }
      os.close();
      System.err.println("Inserting test data.");
      HighLevelSimpleClient client = node.clientCore.makeClient((short) 0);
      InsertBlock block = new InsertBlock(data, new ClientMetadata(), FreenetURI.EMPTY_CHK_URI);
      long startInsertTime = System.currentTimeMillis();
      FreenetURI uri;
      try {
        uri = client.insert(block, false, null);
      } catch (InsertException e) {
        System.err.println("INSERT FAILED: " + e);
        e.printStackTrace();
        System.exit(EXIT_INSERT_FAILED);
        return;
      }
      long endInsertTime = System.currentTimeMillis();
      System.err.println(
          "RESULT: Insert took "
              + (endInsertTime - startInsertTime)
              + "ms ("
              + TimeUtil.formatTime(endInsertTime - startInsertTime)
              + ") to "
              + uri
              + " .");
      node.park();

      // Bootstrap a second node.
      File secondInnerDir = new File(dir, Integer.toString(DARKNET_PORT2));
      secondInnerDir.mkdir();
      fis = new FileInputStream(seednodes);
      FileUtil.writeTo(fis, new File(secondInnerDir, "seednodes.fref"));
      fis.close();
      executor = new PooledExecutor();
      secondNode =
          NodeStarter.createTestNode(
              DARKNET_PORT2,
              OPENNET_PORT2,
              dir.getPath(),
              false,
              Node.DEFAULT_MAX_HTL,
              0,
              random,
              executor,
              1000,
              5 * 1024 * 1024,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              12 * 1024,
              false,
              true,
              false,
              false,
              ipOverride);
      secondNode.start(true);
      if (!TestUtil.waitForNodes(secondNode)) {
        secondNode.park();
        System.exit(EXIT_FAILED_TARGET);
      }

      // Fetch the data
      long startFetchTime = System.currentTimeMillis();
      client = secondNode.clientCore.makeClient((short) 0);
      try {
        client.fetch(uri);
      } catch (FetchException e) {
        System.err.println("FETCH FAILED: " + e);
        e.printStackTrace();
        System.exit(EXIT_FETCH_FAILED);
        return;
      }
      long endFetchTime = System.currentTimeMillis();
      System.err.println(
          "RESULT: Fetch took "
              + (endFetchTime - startFetchTime)
              + "ms ("
              + TimeUtil.formatTime(endFetchTime - startFetchTime)
              + ") of "
              + uri
              + " .");
      secondNode.park();
      System.exit(0);
    } catch (Throwable t) {
      System.err.println("CAUGHT: " + t);
      t.printStackTrace();
      try {
        if (node != null) node.park();
      } catch (Throwable t1) {
      }
      ;
      try {
        if (secondNode != null) secondNode.park();
      } catch (Throwable t1) {
      }
      ;

      System.exit(EXIT_THREW_SOMETHING);
    }
  }
 public OutputStream getOutputStream() throws IOException {
   if (freed) throw new IOException("Already freed");
   return bucket.getOutputStream();
 }