/* Copyright (c) 2006-2009 Jan S. Rellermeyer * Systems Group, * Department of Computer Science, ETH Zurich. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - Neither the name of ETH Zurich nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package ch.ethz.iks.r_osgi.impl; import ch.ethz.iks.r_osgi.types.Timestamp; import ch.ethz.iks.util.MathUtils; /** * <p> * Encapsulates an offset that a remote peer's clock has. * </p> * <p> * Used to transform <code>Timestamp</code> objects received from remote peers * to local time. * </p> * * @author Jan S. Rellermeyer, ETH Z�rich. * @since 0.2 */ final class TimeOffset { /** * the offset. */ private long offset; /** * the last update time. */ private long lastUpdate; /** * the lifetime of this offset. */ private int lifetime; /** * the length of the next series. */ private int seriesLength; /** * the initial lifetime in minutes. */ private static final int INITIAL_LIFETIME = 1; /** * the maximum lifetime. */ private static final int MAX_LIFETIME = 10; /** * the minimum lifetime. */ private static final int MIN_LIFETIME = 1; /** * the default series length. */ private static final int DEFAULT_SERIES = 8; /** * the maximum series length. */ private static final int MAX_SERIES = 32; /** * the minimum series length. */ private static final int MIN_SERIES = 4; /** * creates a new TimeOffset object. * * @param timeSerie * a series of time measurements. */ TimeOffset(final long[] timeSerie) { // initially redo after half a minute lifetime = INITIAL_LIFETIME; seriesLength = DEFAULT_SERIES; update(timeSerie); } /** * update the offset with a new series. * * @param timeSeries * the series. */ void update(final long[] timeSeries) { final int len = timeSeries.length / 2; final long[] offsets = new long[len]; long l = 0; long h = 0; for (int i = 0; i < len; i++) { final long local = timeSeries[2 * i]; final long remote = timeSeries[2 * i + 1]; offsets[i] = local - remote; l = l + MathUtils.lower32(offsets[i]); h = h + MathUtils.higher32(offsets[i]); } final int shift = MathUtils.log2(len); l = l >>> shift; h = h << (32 - shift); final long mean = (h | l); if (lastUpdate != 0) { // heuristic adaptation of seriesLength final long stddev = (MathUtils.max(offsets) - MathUtils .min(offsets)) / 3; /* * System.out.println("STDDEV " + stddev); */ if (stddev < 10) { seriesLength /= 2; } if (stddev > 40) { seriesLength *= 2; } if (stddev > 100) { seriesLength *= 2; } // heuristic adaptation of lifetime final long diff = MathUtils.abs(offset - mean); /* * System.out.println("DIFF " + diff); */ if (diff < (50 - 0.5 * (lifetime * lifetime))) { lifetime++; } if (diff > 100) { lifetime--; } if (diff > 500) { lifetime -= 4; } if (seriesLength > MAX_SERIES) { seriesLength = MAX_SERIES; } if (seriesLength < MIN_SERIES) { seriesLength = MIN_SERIES; } if (lifetime > MAX_LIFETIME) { lifetime = MAX_LIFETIME; } if (lifetime < MIN_LIFETIME) { lifetime = MIN_LIFETIME; } } lastUpdate = System.currentTimeMillis(); offset = mean; } /** * has this time offset expired ? * * @return <code>true</code> if expired, <code>false</code> otherwise. */ boolean isExpired() { return (System.currentTimeMillis() - lastUpdate > lifetime * 60000L); } /** * the length of the series. Note that this value is <i>i</i> = <i>2 * n</i> * or in other words two times the number of rounds. * * @return the length of the series. */ int seriesLength() { return seriesLength; } /** * transform a remote timestamp. * * @param remoteTimestamp * the remote timestamp. * @return the timestamp transformed into local time. */ Long transform(final Long remoteTimestamp) { return new Long(remoteTimestamp.longValue() + offset); } /** * transform a timestamp. * * @param timestamp * the timestamp. * @return the transformed timestamp. */ Timestamp transform(final Timestamp timestamp) { return Timestamp.from((timestamp.getValue() + 1000 * offset)); } }