package org.kne.cloud.network.ntp; import java.io.Closeable; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.NTPTimestamps; import org.kne.cloud.network.MultiProtocolSocketAddress; import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer; import org.kne.math.Long128; public class NTPContext implements Closeable, AutoCloseable { private HighAccuracyClock clock; private static final boolean debug = false; private static final int REQUEST_COUNT = 5; private Long128 systemFrequencyOffset = NTPTimestamps.nanosToNtp128BitTimeInterval(new Long128(5000)); private Long128 localPrecision = NTPTimestamps.nanosToNtp128BitTimeInterval(new Long128(1000)); private static final Long128 adjustThreshold0 = Long128.valueOf(1000000000L); private static final Long128 adjustThreshold1 = Long128.valueOf(100000000L); private AtomicBoolean firstSync=new AtomicBoolean(true); private int minStratum = 16; private volatile PeerInfo currentClock = new PeerInfo(); private volatile boolean closed = false; private Runnable send = new Runnable() { @Override public void run() { while (!closed) { try { clearPackets(); Map> mlp = new HashMap<>(); ios.forEach((v) -> { mlp.put(v, v.getPeersWillSend()); }); for (int i = 0; i < REQUEST_COUNT; i++) { Set>> mlps = mlp.entrySet(); for (Iterator>> iterator = mlps.iterator(); iterator .hasNext();) { Entry> object = iterator.next(); List val = object.getValue(); for (NTPPeer perr : val) { try { object.getKey().request(perr); } catch (IOException e) { if (debug) e.printStackTrace(); } } } Thread.sleep(100); } Thread.sleep(500); if (!mlp.isEmpty()) mergeAndApply(); } catch (InterruptedException e) { e.printStackTrace(); } } } }; public NTPContext(HighAccuracyClock clock) { this.clock = clock; Thread ts = new Thread(send); ts.setName("NTPv4 Send Thread"); ts.start(); } public void syncToSystem() { clock.syncToClock(new HighAccuracyClock()); minStratum = 15; currentClock.stratum = Math.min(currentClock.stratum, minStratum); currentClock.leapIndicator = 0; } public int getMinStratum() { return minStratum; } public void setMinStratum(int minStratum) { this.minStratum = minStratum; } public int getStratum() { return currentClock.stratum; } public HighAccuracyClock getClock() { return clock; } private ConcurrentHashMap> recvmap = new ConcurrentHashMap>(); protected void clearPackets() { recvmap.clear(); } protected void putPacket(NTPv4Packet nv4, MultiProtocolSocketAddress inetSocketAddress) { checkIP(); List newv = new Vector(); List oldv = recvmap.putIfAbsent(inetSocketAddress, newv); if (oldv == null) { oldv = newv; } synchronized (oldv) { oldv.add(nv4); while (oldv.size() > 10) { oldv.remove(0); } } } private void checkIP() { Set>> ens = recvmap.entrySet(); for (Iterator>> iterator = ens.iterator(); iterator .hasNext();) { Entry> entry = (Entry>) iterator .next(); AtomicBoolean ab = new AtomicBoolean(false); ios.forEach((x) -> { if (!ab.get()) if (x.findPeer(entry.getKey()) != null) { ab.set(true); return; } }); if (!ab.get()) { iterator.remove(); } } } private class PeerInfo implements Comparable { private MultiProtocolSocketAddress address; private int leapIndicator = 3; private int stratum = 16; private int referenceIdentifier; private int pollInterval; private byte precision; private long rootDelay = Integer.MAX_VALUE; private long rootDispersion = Integer.MAX_VALUE; private Long128 referenceTimestamp = Long128.ZERO; private Long128 uploadDelay; private Long128 downloadDelay; private Long128 rtt; @Override public String toString() { return "PeerInfo [address=" + address + ", leapIndicator=" + leapIndicator + ", stratum=" + stratum + ", referenceIdentifier=" + referenceIdentifier + ", pollInterval=" + pollInterval + ", precision=" + precision + ", rootDelay=" + rootDelay + ", rootDispersion=" + rootDispersion + ", referenceTimestamp=" + referenceTimestamp + ", uploadDelay=" + uploadDelay + ", downloadDelay=" + downloadDelay + "]"; } private long getRootDistance() { return rootDelay / 2 + rootDispersion; } @Override public int compareTo(PeerInfo o) { return Long.compare(getRootDistance(), o.getRootDistance()); } public Long128 getCurrentSelfDispersion128() { Long128 vk = (clock.getCurrentTimeNTP128().subtract(referenceTimestamp)).multiply(systemFrequencyOffset) .divide(Long128.ONE.shiftLeft(64)); Long128 vkl; if (vk.signum() < 0) { vkl = Long128.ZERO; } else { vkl = vk; } return vkl; } public long getCurrentRootDispersion() { Long128 vkl = getCurrentSelfDispersion128().shiftRight(16 + 32); long rez = rootDispersion + vkl.longValue(); if (rez > Integer.MAX_VALUE) { rez = Integer.MAX_VALUE; } return rez; } public Long128 getAdj() { return NTPTimestamps.ntp128BitToNanosInterval(uploadDelay.subtract(downloadDelay).shiftRight(1)); } } public void mergeAndApply() { List peerInfo = mergeResponses(); selectAndApply(peerInfo); } private long avgAdj=0; private Long128 inte=new Long128(0); private void selectAndApply(List peerInfo) { Collections.sort(peerInfo); if(debug) System.out.println(peerInfo); if (!peerInfo.isEmpty()) { PeerInfo pix = peerInfo.get(0); currentClock = pix; int i; int m = Math.min(peerInfo.size(), 3); Long128 bi = Long128.ZERO; for (i = 0; i < m; i++) { PeerInfo pi = peerInfo.get(i); Long128 adjt = pi.getAdj(); bi = bi.add(adjt); } Long128 delta = bi.divide(Long128.valueOf(i)); avgAdj=(avgAdj*7+delta.abs().longValue())/8; Long128 deltaabs=delta.abs(); if (deltaabs.compareTo(adjustThreshold0) > 0&&firstSync.compareAndSet(true, false)) { Long128 adjustment=delta; clock.adjustClock( adjustment); if(debug) System.out.println("adj:"+delta); }else if (deltaabs.compareTo(adjustThreshold1) > 0) { Long128 adjustment = Long128.valueOf(new BigDecimal(delta.toBigInteger()).multiply(BigDecimal.valueOf(0.5)).toBigInteger()); clock.adjustClock( adjustment); if(debug) System.out.println("adj:"+delta); } else { Long128 fadj = delta.divide(20).add(inte.divide(200)); clock.setFrequency(1000000000L+fadj.longValue()); inte=inte.add(delta); if(inte.compareTo(Long128.valueOf(5000000))>0) { inte=Long128.valueOf(5000000); }else if(inte.compareTo(Long128.valueOf(-5000000))<0) { inte=Long128.valueOf(-5000000); } if(debug) System.out.println("inte:"+inte+" fadj:" + fadj); } if(debug) System.out.println("delta:"+delta); } } private List mergeResponses() { List peerInfo = new ArrayList(); Set>> ens = recvmap.entrySet(); for (Iterator>> iterator = ens.iterator(); iterator .hasNext();) { Entry> entry = (Entry>) iterator .next(); List newv = entry.getValue(); PeerInfo pi = null; for (NTPv4Packet pack : newv) { int li = pack.getLeapIndicator(); if (li == 3) { continue; } int stratum = pack.getStratum(); if (stratum == 0) { stratum = 16; } int mode = pack.getMode(); switch (mode) { case NTPv4Packet.NTP_SERVER: stratum += 1; break; case NTPv4Packet.NTP_SYMMETRIC_PASSIVE: if (stratum < minStratum) stratum += 1; break; } if (stratum >= 16) { continue; } if (pi == null) { pi = new PeerInfo(); pi.address = entry.getKey(); } pi.leapIndicator = pack.getLeapIndicator(); pi.stratum = stratum; pi.referenceIdentifier = pack.getReferenceIdentifier(); pi.pollInterval = pack.getPollInterval(); pi.precision = pack.getPrecision(); pi.rootDelay = pack.getRootDelay();// (1/65536.0*1000000000) pi.rootDispersion = pack.getRootDispersion(); pi.referenceTimestamp = pack.getReferenceTimestamp128(); Long128 uploadD = pack.getReceiveTimestamp128().subtract(pack.getOriginateTimestamp128()); Long128 downloadD = pack.getDestinationTimestamp128().subtract(pack.getTransmitTimestamp128()); Long128 rtt = uploadD.add(downloadD); if (pi.uploadDelay == null) { pi.uploadDelay = uploadD; } else { if (pi.uploadDelay.compareTo(uploadD) > 0) { pi.uploadDelay = uploadD; } } if (pi.downloadDelay == null) { pi.downloadDelay = downloadD; } else { if (pi.downloadDelay.compareTo(downloadD) > 0) { pi.downloadDelay = downloadD; } } if (pi.rtt == null) { pi.rtt = rtt; } else { if (pi.rtt.compareTo(rtt) > 0) { pi.rtt = rtt; } } } if (pi != null) { long ndl = pi.rootDelay + (pi.rtt).shiftRight(32 + 16).longValue(); // System.out.println(pi.rtt+" "+pi.rootDelay+" "+ndl); pi.rootDelay = Math.min(ndl, Integer.MAX_VALUE); long ndsp = pi.rootDispersion + Math.max(localPrecision.shiftRight(16 + 32).longValue(), 1); pi.rootDispersion = Math.min(ndsp, Integer.MAX_VALUE); peerInfo.add(pi); } } return peerInfo; } @Override public String toString() { return "NTPContext [clock=" + clock + ", systemFrequencyOffset=" + systemFrequencyOffset + ", minStratum=" + minStratum + ", leapIndicator=" + currentClock.leapIndicator + ", stratum=" + currentClock.stratum + ", referenceIdentifier=" + currentClock.referenceIdentifier + ", pollInterval=" + currentClock.pollInterval + ", localPrecision=" + localPrecision + ", rootDelay=" + currentClock.rootDelay + ", rootDispersion=" + currentClock.rootDispersion + ", referenceTimestamp=" + NTPTimestamps.ntp128ToString(currentClock.referenceTimestamp) + "]"; } public Long128 getSystemFrequencyOffset() { return systemFrequencyOffset; } public void setSystemFrequencyOffset(Long128 systemFrequencyOffset) { this.systemFrequencyOffset = systemFrequencyOffset; } public Long128 getLocalPrecision() { return localPrecision; } public void setLocalPrecision(Long128 localPrecision) { this.localPrecision = localPrecision; } public int getLeapIndicator() { return currentClock.leapIndicator; } public int getReferenceIdentifier() { return currentClock.referenceIdentifier; } public int getPollInterval() { return currentClock.pollInterval; } public long getRootDelay() { return currentClock.rootDelay; } public long getRootDispersion() { return currentClock.rootDispersion; } public Long128 getReferenceTimestamp() { return currentClock.referenceTimestamp; } public Long128 getReferenceTimestamp64() { return NTPTimestamps.ntp128To64(currentClock.referenceTimestamp); } public Long128 getCurrentSelfDispersion128() { return currentClock.getCurrentSelfDispersion128(); } public long getCurrentRootDispersion() { return currentClock.getCurrentRootDispersion(); } private static Set ios = Collections.synchronizedSet(new HashSet<>()); public void registerIO(NTPv4Protocol ntPv4Protocol) { ios.add(ntPv4Protocol); } public void unregisterIO(NTPv4Protocol ntPv4Protocol) { ios.remove(ntPv4Protocol); } public boolean isClosed() { return closed; } @Override public void close() throws IOException { closed = true; } }