/*
 * Copyright 2010-2011 Roger Kapsi
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package org.ardverk.security.token;

import java.io.Closeable;
import java.security.MessageDigest;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.ardverk.concurrent.ExecutorUtils;
import org.ardverk.concurrent.FutureUtils;
import org.ardverk.security.SecurityUtils;

/**
 * A default implementation of {@link SecurityToken}.
 */
public class DefaultSecurityToken extends AbstractSecurityToken implements Closeable {

    private static final ScheduledExecutorService EXECUTOR 
        = ExecutorUtils.newSingleThreadScheduledExecutor("SecurityTokenThread");
    
    private static final Random RANDOM = SecurityUtils.createSecureRandom();
    
    private final MessageDigest messageDigest;
    
    private final ScheduledFuture<?> future;
    
    private volatile byte[] current = null;
    
    private volatile byte[] previous = null;
    
    public DefaultSecurityToken(MessageDigest messageDigest, 
            long frequency, TimeUnit unit) {
        this(EXECUTOR, messageDigest, RANDOM, frequency, unit);
    }
    
    public DefaultSecurityToken(ScheduledExecutorService executor, 
            MessageDigest messageDigest, final Random random, 
            long frequency, TimeUnit unit) {
        
        this.messageDigest = messageDigest;
        
        // TODO: Hmm, is this sufficient?
        current = new byte[messageDigest.getDigestLength()];
        random.nextBytes(current);
        
        ScheduledFuture<?> future = null;
        if (0 < frequency) {
            Runnable task = new Runnable() {
                @Override
                public void run() {
                    synchronized (DefaultSecurityToken.this) {
                        previous = current;
                        
                        // Create a new byte[] of the same length
                        current = new byte[current.length];
                        random.nextBytes(current);
                    }
                }
            };
            
            future = executor.scheduleWithFixedDelay(
                    task, frequency, frequency, unit);
        }
        
        this.future = future;
    }
    
    @Override
    public void close() {
        FutureUtils.cancel(future, true);
    }
    
    @Override
    public byte[] create(byte[] data, int offset, int length) {
        return create(current, data, offset, length);
    }
    
    private synchronized byte[] create(byte[] key, byte[] data, 
            int offset, int length) {
        messageDigest.reset();
        
        messageDigest.update(key);
        messageDigest.update(data, offset, length);
        
        return messageDigest.digest();
    }
    
    @Override
    public boolean isFor(byte[] data, int dataOffset, int dataLength, 
            byte[] securityToken, int offset, int length) {
        
        byte[] current = null;
        byte[] previous = null;
        
        synchronized (this) {
            current = this.current;
            previous = this.previous;
        }
        
        if (current != null) {
            byte[] expected = create(current, data, dataOffset, dataLength);
            if (equals(expected, securityToken, offset, length)) {
                return true;
            }
            
            if (previous != null) {
                expected = create(previous, data, dataOffset, dataLength);
                return equals(expected, securityToken, offset, length);
            }
        }
        
        return false;
    }
    
    private static boolean equals(byte[] expected, 
            byte[] actual, int offset, int length) {
        
        if (expected.length == length) {
            for (int i = 0; i < length; i++) {
                if (expected[i] != actual[offset + i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
}

@@ -1376,32 +1376,96 @@
 eRandom();%0A    %0A
+    private final Object messageDigestLock = new Object();%0A    %0A
     private fina
@@ -1554,39 +1554,84 @@
    %0A    private 
-volatil
+final Object keyLock = new Object();%0A    %0A    privat
 e byte%5B%5D current
@@ -1660,17 +1660,8 @@
 ate 
-volatile 
 byte
@@ -2468,33 +2468,15 @@
 ed (
-DefaultSecurityToken.this
+keyLock
 ) %7B%0A
@@ -3121,32 +3121,160 @@
 , int length) %7B%0A
+        %0A        byte%5B%5D current = null;%0A        synchronized (keyLock) %7B%0A            current = this.current;%0A        %7D%0A        %0A
         return c
@@ -3334,29 +3334,16 @@
 private 
-synchronized 
 byte%5B%5D c
@@ -3412,32 +3412,88 @@
 ngth) %7B%0A        
+%0A        synchronized (messageDigestLock) %7B%0A            
 messageDigest.re
@@ -3499,33 +3499,41 @@
 eset();%0A        
+  
-%0A
+  %0A    
         messageD
@@ -3551,16 +3551,20 @@
 e(key);%0A
+    
         
@@ -3607,32 +3607,36 @@
 ength);%0A        
+    
 %0A        return 
@@ -3624,24 +3624,28 @@
    %0A        
+    
 return messa
@@ -3659,24 +3659,34 @@
 t.digest();%0A
+        %7D%0A
     %7D%0A    %0A 
@@ -3932,20 +3932,23 @@
 onized (
-this
+keyLock
 ) %7B%0A    

Fix for race condition (can't use volatile)