/* * [PadInfo.java] * * Summary: info about one pad file. * * Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2012-12-30 initial release. */ package com.mindprod.otp; import com.mindprod.common18.EIO; import com.mindprod.filter.ExtensionListFilter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map.Entry; import java.util.zip.GZIPOutputStream; import static java.lang.System.*; /** * info about one pad file. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2012-12-30 initial release. * @since 2012-12-30 */ public final class PadInfo implements Serializable { /** * Layout version number for serialization. */ private static final long serialVersionUID = 2L; /** * invenory of all pads in existence. */ private static HashMap inventory; private static long padBytesRemaining; private static int padsAppearing; private static int padsDisappearing; /** * name of pad file without qualification */ private final String padFilename; /** * how long the pad file is. Never changes length even when wiped */ private final long length; /** * true if there is no longer a physical file corresponding to this entry. */ private boolean isMissing; /** * next byte we can use of the pad file. */ private long offset; private PadInfo( final String padFilename, final long length ) { this.padFilename = padFilename; this.length = length; this.offset = 0; } long getLength() { return length; } long getOffset() { return offset; } public void setOffset( final long offset ) { this.offset = offset; } String getPadFilename() { return padFilename; } long getRemaining() { return length - offset; } boolean isMissing() { return isMissing; } void setMissing( final boolean isMissing ) { this.isMissing = isMissing; } /** * find best Pad to service need for this many pad bytse * * @param padBytesNeeded how many bytes we need in total for header and file. * * @return Best pad, none if none with sufficient bytes. */ public static PadInfo findBestPad( long padBytesNeeded ) { // the best is defined as file having least remaining, but still enough. // It wants to save the long pads for long files to encode. // Oldest pad might be an alternate criteria. We don't do that though. PadInfo best = null; long bestRemaining = Long.MAX_VALUE; for ( PadInfo padInfo : inventory.values() ) { long remaining = padInfo.getRemaining(); if ( remaining >= padBytesNeeded && remaining < bestRemaining ) { best = padInfo; } } return best; } /** * how many random bytes are left to use in the pad files? * * @return count */ public static long getPadBytesRemaining() { return padBytesRemaining; } /** * how many pad appeared in current dir since last time * * @return count */ public static int getPadsAppearing() { return padsAppearing; } /** * how many pads disappeared since last time * * @return count */ public static int getPadsDisappearing() { // todo: check that files we kill will not count. return padsDisappearing; } /** * load previous inventory of PadInfos */ public static void loadInventory() throws IOException, ClassNotFoundException { // O P E N File inventoryfile = new File( OTP.keysDir, "padinventory.ser" ); if ( inventoryfile.canRead() ) { final ObjectInputStream ois = EIO.getObjectInputStream( inventoryfile, 4 * 1024, true ); // R E A D if ( serialVersionUID != ois.readLong() ) { err.println( "Files are incompatible with this version of the program. Please start over." ); System.exit( 2 ); } final PadInfo[] padInfos = ( PadInfo[] ) ois.readObject(); inventory = new HashMap<>( padInfos.length * 120 / 100 ); for ( PadInfo padInfo : padInfos ) { inventory.put( padInfo.getPadFilename(), padInfo ); } // C L O S E ois.close(); } else { // start afresh with an empty map. // padsAppearing will let user know something unusual is happening. inventory = new HashMap<>( 1000 ); } } /** * save our inventory of PadInfos */ public static void saveInventory() throws IOException { // get rid of any junk, pad files that have shriveled to nothing. for ( PadInfo padInfo : inventory.values() ) { if ( padInfo.getRemaining() < OTP.MININUM_PAD_SIZE ) { inventory.remove( padInfo.getPadFilename() ); boolean success = new File( OTP.keysDir, padInfo.getPadFilename() ).delete(); if ( !success ) { err.println( "Could not delete empty pad " + padInfo.getPadFilename() ); } } } // O P E N File inventoryfile = new File( OTP.keysDir, "padinventory.ser" ); final FileOutputStream fos = new FileOutputStream( inventoryfile, false /* append */ ); final GZIPOutputStream gzos = new GZIPOutputStream( fos, 4 * 1024 /* buffsize in bytes */ ); final ObjectOutputStream oos = new ObjectOutputStream( gzos ); // W R I T E oos.writeLong( serialVersionUID ); // convert HashMap to array for serialisation oos.writeObject( inventory.values().toArray( new PadInfo[ inventory.size() ] ) ); // C L O S E oos.close(); } /** * Update inventory of PadInfos. */ public static void updateInventory() { // Detect missing physical files. Presume missing. for ( PadInfo padInfo : inventory.values() ) { padInfo.setMissing( true ); } File current = OTP.keysDir; String[] pads = current.list( new ExtensionListFilter( "pad" ) ); for ( String pad : pads ) { PadInfo old = inventory.get( pad ); if ( old != null ) { // already known File padFile = new File( OTP.keysDir, pad ); if ( padFile.length() != old.getLength() ) { throw new IllegalArgumentException( "pad file length tampered with." ); } padBytesRemaining += old.getLength() - old.getOffset(); old.setMissing( false ); } else { // new File padFile = new File( OTP.keysDir, pad ); inventory.put( pad, new PadInfo( pad, padFile.length() ) ); padBytesRemaining += padFile.length(); padsAppearing++; } } // PadInfos without flag have been deleted, remove from inventory for ( Entry entry : inventory.entrySet() ) { if ( entry.getValue().isMissing() ) { padsDisappearing++; inventory.remove( entry.getKey() ); } } } }