Package freenet.node.updater

Source Code of freenet.node.updater.RevocationChecker

package freenet.node.updater;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import freenet.client.FetchContext;
import freenet.client.FetchException;
import freenet.client.FetchException.FetchExceptionMode;
import freenet.client.FetchResult;
import freenet.client.async.BinaryBlobWriter;
import freenet.client.async.ClientContext;
import freenet.client.async.ClientGetCallback;
import freenet.client.async.ClientGetter;
import freenet.client.async.PersistenceDisabledException;
import freenet.l10n.NodeL10n;
import freenet.node.NodeClientCore;
import freenet.node.RequestClient;
import freenet.node.RequestStarter;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.MediaType;
import freenet.support.api.Bucket;
import freenet.support.api.RandomAccessBucket;
import freenet.support.api.RandomAccessBuffer;
import freenet.support.io.ArrayBucket;
import freenet.support.io.BucketTools;
import freenet.support.io.ByteArrayRandomAccessBuffer;
import freenet.support.io.FileBucket;
import freenet.support.io.FileUtil;
import freenet.support.io.FileRandomAccessBuffer;

/**
* Fetches the revocation key. Each time it starts, it will try to fetch it until it has 3 DNFs. If it ever finds it, it will
* be immediately fed to the NodeUpdateManager.
*/
public class RevocationChecker implements ClientGetCallback, RequestClient {

  public final static int REVOCATION_DNF_MIN = 3;
 
  private boolean logMINOR;

  private NodeUpdateManager manager;
  private NodeClientCore core;
  private int revocationDNFCounter;
  private FetchContext ctxRevocation;
  private ClientGetter revocationGetter;
  private boolean wasAggressive;
  /** Last time at which we got 3 DNFs on the revocation key */
  private long lastSucceeded;
  // Kept separately from NodeUpdateManager.hasBeenBlown because there are local problems that can blow the key.
  private volatile boolean blown;
 
  private File blobFile;
  /** The original binary blob bucket. */
  private ArrayBucket blobBucket;

  public RevocationChecker(NodeUpdateManager manager, File blobFile) {
    this.manager = manager;
    core = manager.node.clientCore;
    this.revocationDNFCounter = 0;
    this.blobFile = blobFile;
    this.logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
    ctxRevocation = core.makeClient((short)0, true, false).getFetchContext();
    // Do not allow redirects etc.
    // If we allow redirects then it will take too long to download the revocation.
    // Anyone inserting it should be aware of this fact!
    // You must insert with no content type, and be less than the size limit, and less than the block size after compression!
    // If it doesn't fit, we'll still tell the user, but the message may not be easily readable.
    ctxRevocation.allowSplitfiles = false;
    ctxRevocation.maxArchiveLevels = 0;
    ctxRevocation.followRedirects = false;
    // big enough ?
    ctxRevocation.maxOutputLength = NodeUpdateManager.MAX_REVOCATION_KEY_LENGTH;
    ctxRevocation.maxTempLength = NodeUpdateManager.MAX_REVOCATION_KEY_TEMP_LENGTH;
    ctxRevocation.maxSplitfileBlockRetries = -1; // if we find content, try forever to get it; not used because of the above size limits.
    ctxRevocation.maxNonSplitfileRetries = 0; // but return quickly normally
  }
 
  public int getRevocationDNFCounter() {
    return revocationDNFCounter;
  }

  public void start(boolean aggressive) {
    start(aggressive, true);
    if(blobFile.exists()) {
      ArrayBucket bucket = new ArrayBucket();
      try {
        BucketTools.copy(new FileBucket(blobFile, true, false, false, true), bucket);
        // Allow to free if bogus.
        manager.uom.processRevocationBlob(bucket, "disk", true);
      } catch (IOException e) {
        Logger.error(this, "Failed to read old revocation blob: "+e, e);
        System.err.println("We may have downloaded an old revocation blob before restarting but it cannot be read: "+e);
        e.printStackTrace();
      }
    }
  }
 
  /** Start a fetch.
   * @param aggressive If set to true, then we have just fetched an update, and therefore can increase the priority of the
   * fetch to maximum.
   * @return True if the checker was already running and the counter was not reset.
   * */
  public boolean start(boolean aggressive, boolean reset) {
   
    if(manager.isBlown()) {
      Logger.error(this, "Not starting revocation checker: key already blown!");
      return false;
    }
    boolean wasRunning = false;
    logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
    ClientGetter cg = null;
    try {
      ClientGetter toCancel = null;
      synchronized(this) {
        if(aggressive && !wasAggressive) {
          // Ignore old one.
          toCancel = revocationGetter;
          if(logMINOR) Logger.minor(this, "Ignoring old request, because was low priority");
          revocationGetter = null;
          if(toCancel != null) wasRunning = true;
        }
        wasAggressive = aggressive;
        if(revocationGetter != null &&
            !(revocationGetter.isCancelled() || revocationGetter.isFinished()))  {
          if(logMINOR) Logger.minor(this, "Not queueing another revocation fetcher yet, old one still running");
          reset = false;
          wasRunning = false;
        } else {
          if(reset) {
            if(logMINOR) Logger.minor(this, "Resetting DNF count from "+revocationDNFCounter, new Exception("debug"));
            revocationDNFCounter = 0;
          } else {
            if(logMINOR) Logger.minor(this, "Revocation count "+revocationDNFCounter);
          }
          if(logMINOR) Logger.minor(this, "fetcher="+revocationGetter);
          if(revocationGetter != null && logMINOR) Logger.minor(this, "revocation fetcher: cancelled="+revocationGetter.isCancelled()+", finished="+revocationGetter.isFinished());
          // Client startup may not have completed yet.
          manager.node.clientCore.getPersistentTempDir().mkdirs();
          cg = revocationGetter = new ClientGetter(this,
              manager.getRevocationURI(), ctxRevocation,
              aggressive ? RequestStarter.MAXIMUM_PRIORITY_CLASS : RequestStarter.IMMEDIATE_SPLITFILE_PRIORITY_CLASS,
              null, new BinaryBlobWriter(new ArrayBucket()), null);
          if(logMINOR) Logger.minor(this, "Queued another revocation fetcher (count="+revocationDNFCounter+")");
        }
      }
      if(toCancel != null)
        toCancel.cancel(core.clientContext);
      if(cg != null) {
        core.clientContext.start(cg);
        if(logMINOR) Logger.minor(this, "Started revocation fetcher");
      }
      return wasRunning;
    } catch (FetchException e) {
      if(e.mode == FetchExceptionMode.RECENTLY_FAILED) {
        Logger.error(this, "Cannot start revocation fetcher because recently failed");
      } else {
        Logger.error(this, "Cannot start fetch for the auto-update revocation key: "+e, e);
        manager.blow("Cannot start fetch for the auto-update revocation key: "+e, true);
      }
      synchronized(this) {
        if(revocationGetter == cg) {
          revocationGetter = null;
        }
      }
      return false;
    } catch (PersistenceDisabledException e) {
      // Impossible
      return false;
    }
  }

  long lastSucceeded() {
    return lastSucceeded;
  }
 
  long lastSucceededDelta() {
    if(lastSucceeded <= 0) return -1;
    return System.currentTimeMillis() - lastSucceeded;
  }
 
  /** Called when the revocation URI changes. */
  public void onChangeRevocationURI() {
    kill();
    start(wasAggressive);
  }

  @Override
  public void onSuccess(FetchResult result, ClientGetter state) {
    onSuccess(result, state, state.getBlobBucket());
  }
 
  void onSuccess(FetchResult result, ClientGetter state, Bucket blob) {
    // The key has been blown !
    // FIXME: maybe we need a bigger warning message.
    blown = true;
    moveBlob(blob);
    String msg = null;
    try {
      byte[] buf = result.asByteArray();
      msg = new String(buf, MediaType.getCharsetRobustOrUTF(result.getMimeType()));
    } catch (Throwable t) {
      try {
        msg = "Failed to extract result when key blown: "+t;
        Logger.error(this, msg, t);
        System.err.println(msg);
        t.printStackTrace();
      } catch (Throwable t1) {
        msg = "Internal error after retreiving revocation key";
      }
    }
    manager.blow(msg, false); // Real one, even if we can't extract the message.
  }
 
  public boolean hasBlown() {
    return blown;
  }

  private void moveBlob(Bucket tmpBlob) {
    if(tmpBlob == null) {
      Logger.error(this, "No temporary binary blob file moving it: may not be able to propagate revocation, bug???");
      return;
    }
    if(tmpBlob instanceof ArrayBucket) {
      synchronized(this) {
        if(tmpBlob == blobBucket) return;
        blobBucket = (ArrayBucket) tmpBlob;
      }
    } else {
      try {
        ArrayBucket buf = new ArrayBucket(BucketTools.toByteArray(tmpBlob));
        synchronized(this) {
          blobBucket = buf;
        }
      } catch (IOException e) {
        System.err.println("Unable to copy data from revocation bucket!");
        System.err.println("This should not happen and indicates there may be a problem with the auto-update checker.");
        // Don't blow(), as that's already happened.
        return;
      }
      if(tmpBlob instanceof FileBucket) {
        File f = ((FileBucket)tmpBlob).getFile();
        synchronized(this) {
          if(f == blobFile) return;
          if(f.equals(blobFile)) return;
          if(FileUtil.getCanonicalFile(f).equals(FileUtil.getCanonicalFile(blobFile))) return;
        }
      }
      System.out.println("Unexpected blob file in revocation checker: "+tmpBlob);
    }
    FileBucket fb = new FileBucket(blobFile, false, false, false, false);
    try {
      BucketTools.copy(tmpBlob, fb);
    } catch (IOException e) {
      System.err.println("Got revocation but cannot write it to disk: "+e);
      System.err.println("This means the auto-update system is blown but we can't tell other nodes about it!");
      e.printStackTrace();
    }
  }

  @Override
  public void onFailure(FetchException e, ClientGetter state) {
    onFailure(e, state, state.getBlobBucket());
  }
 
  void onFailure(FetchException e, ClientGetter state, Bucket blob) {
    logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
    if(logMINOR) Logger.minor(this, "Revocation fetch failed: "+e);
    FetchExceptionMode errorCode = e.getMode();
    boolean completed = false;
    long now = System.currentTimeMillis();
    if(errorCode == FetchExceptionMode.CANCELLED) {
      return; // cancelled by us above, or killed; either way irrelevant and doesn't need to be restarted
    }
    if(e.isFatal()) {
      if(!e.isDefinitelyFatal()) {
        // INTERNAL_ERROR could be related to the key but isn't necessarily.
        // FIXME somebody should look at these two strings and de-uglify them!
        // They should never be seen but they should be idiot-proof if they ever are.
        // FIXME split into two parts? Fetch manually should be a second part?
        String message = l10n("revocationFetchFailedMaybeInternalError", new String[] { "detail", "key" }, new String[] { e.toUserFriendlyString(), manager.getRevocationURI().toASCIIString() });
        System.err.println(message);
        e.printStackTrace();
        manager.blow(message, true);
        return;
      }
      // Really fatal, i.e. something was inserted but can't be decoded.
      // FIXME somebody should look at these two strings and de-uglify them!
      // They should never be seen but they should be idiot-proof if they ever are.
      String message = l10n("revocationFetchFailedFatally", new String[] { "detail", "key" }, new String[] { e.toUserFriendlyString(), manager.getRevocationURI().toASCIIString() });     
      manager.blow(message, false);
      moveBlob(blob);
      return;
    }
    if(e.newURI != null) {
      manager.blow("Revocation URI redirecting to "+e.newURI+" - maybe you set the revocation URI to the update URI?", false);
    }
    synchronized(this) {
      if(errorCode == FetchExceptionMode.DATA_NOT_FOUND){
        revocationDNFCounter++;
        if(logMINOR) Logger.minor(this, "Incremented DNF counter to "+revocationDNFCounter);
      }
      if(revocationDNFCounter >= 3) {
        lastSucceeded = now;
        completed = true;
        revocationDNFCounter = 0;
      }
      revocationGetter = null;
    }
    if(completed)
      manager.noRevocationFound();
    else {
      if(errorCode == FetchExceptionMode.RECENTLY_FAILED) {
        // Try again in 1 second.
        // This ensures we don't constantly start them, fail them, and start them again.
        this.manager.node.ticker.queueTimedJob(new Runnable() {

          @Override
          public void run() {
            start(wasAggressive, false);
          }

        }, SECONDS.toMillis(1));
      } else {
        start(wasAggressive, false);
      }
    }
  }
 
  private String l10n(String key, String[] pattern, String[] value) {
    return NodeL10n.getBase().getString("RevocationChecker." + key,
        pattern, value);
  }

  public void kill() {
    if(revocationGetter != null)
      revocationGetter.cancel(core.clientContext);
  }

  public long getBlobSize() {
    return blobFile.length();
  }

  public RandomAccessBucket getBlobBucket() {
    if(!manager.isBlown()) return null;
    synchronized(this) {
      if(blobBucket != null)
        return blobBucket;
    }
    File f = getBlobFile();
    if(f == null) return null;
    return new FileBucket(f, true, false, false, false);
  }
 
  public RandomAccessBuffer getBlobBuffer() {
    if(!manager.isBlown()) return null;
    synchronized(this) {
      if(blobBucket != null) {
          try {
              ByteArrayRandomAccessBuffer t = new ByteArrayRandomAccessBuffer(blobBucket.toByteArray());
              t.setReadOnly();
              return t;
          } catch (IOException e) {
              Logger.error(this, "Impossible: "+e, e);
              return null;
          }
      }
    }
    File f = getBlobFile();
    if(f == null) return null;
    try {
      return new FileRandomAccessBuffer(f, true);
    } catch(FileNotFoundException e) {
      Logger.error(this, "We do not have the blob file for the revocation even though we have successfully downloaded it!", e);
      return null;
    } catch (IOException e) {
            Logger.error(this, "Error reading downloaded revocation blob file: "+e, e);
            return null;
    }
  }
 
  /** Get the binary blob, if we have fetched it. */
  private File getBlobFile() {
    if(blobFile.exists()) return blobFile;
    return null;
  }

  @Override
  public boolean persistent() {
    return false;
  }

  @Override
  public boolean realTimeFlag() {
    return false;
  }

    @Override
    public void onResume(ClientContext context) {
        // Do nothing. Not persistent.
    }

    @Override
    public RequestClient getRequestClient() {
        return this;
    }

}
TOP

Related Classes of freenet.node.updater.RevocationChecker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.