1 // ======================================================================== 2 // Copyright 2006 Mort Bay Consulting Pty. Ltd. 3 // ------------------------------------------------------------------------ 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 // ======================================================================== 14 15 package org.mortbay.cometd.ext; 16 17 import java.util.Map; 18 19 import org.cometd.Client; 20 import org.cometd.Extension; 21 import org.cometd.Message; 22 import org.mortbay.cometd.ClientImpl; 23 import org.mortbay.util.ajax.JSON; 24 25 /* ------------------------------------------------------------ */ 26 /** 27 * Timesync extension (server side). With each handshake or connect, the 28 * extension sends timestamps within the ext field like: 29 * <code>{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}</code> where: 30 * <ul> 31 * <li>tc is the client timestamp in ms since 1970 of when the message was sent. 32 * <li>l is the network lag that the client has calculated. 33 * <li>o is the clock offset that the client has calculated. 34 * </ul> 35 * 36 * <p> 37 * A cometd server that supports timesync, can respond with an ext field like: 38 * <code>{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}</code> 39 * where: 40 * <ul> 41 * <li>tc is the client timestamp of when the message was sent, 42 * <li>ts is the server timestamp of when the message was received 43 * <li>p is the poll duration in ms - ie the time the server took before sending 44 * the response. 45 * <li>a is the measured accuracy of the calculated offset and lag sent by the 46 * client 47 * </ul> 48 * <p> 49 * The relationship between tc, ts, o & l on the server is given by 50 * <code>ts=tc+o+l</code> (the time the server received the message is the 51 * client time plus the offset plus the network lag). Thus the accuracy of the o 52 * and l settings can be determined with <code>a=tc+o+l-ts</code>. 53 * </p> 54 * <p> 55 * When the client has received the response, it can make a more accurate 56 * estimate of the lag as <code>l2=(now-tc-p)/2</code> (assuming symmetric lag). 57 * A new offset can then be calculated with the relationship on the client that 58 * <code>ts=tc+o2+l2</code>, thus <code>o2=ts-tc-l2</code>. 59 * </p> 60 * <p> 61 * Since the client also receives the a value calculated on the server, it 62 * should be possible to analyse this and compensate for some asymmetry in the 63 * lag. But the current client does not do this. 64 * </p> 65 */ 66 public class TimesyncExtension implements Extension 67 { 68 private int _accuracyTarget=25; 69 70 public TimesyncExtension() 71 { 72 } 73 74 /* ------------------------------------------------------------ */ 75 /** 76 * timesync responses are not set if the measured accuracy is less than the 77 * accuracyTarget. 78 * 79 * @return accuracy target in ms (default 25ms) 80 */ 81 public int getAccuracyTarget() 82 { 83 return _accuracyTarget; 84 } 85 86 /* ------------------------------------------------------------ */ 87 /** 88 * timesync responses are not set if the measured accuracy is less than the 89 * accuracyTarget. 90 * 91 * @param target 92 * accuracy target in ms 93 */ 94 public void setAccuracyTarget(int target) 95 { 96 _accuracyTarget=target; 97 } 98 99 public Message rcv(Client from, Message message) 100 { 101 return message; 102 } 103 104 public Message rcvMeta(Client from, Message message) 105 { 106 Map<String,Object> ext=message.getExt(false); 107 if (ext != null) 108 { 109 Map<String,Object> sync=(Map<String,Object>)ext.get("timesync"); 110 if (sync != null) 111 { 112 sync.put("ts",new Long(System.currentTimeMillis())); 113 Number lag=(Number)sync.get("l"); 114 if (lag != null && from != null) 115 ((ClientImpl)from).setLag(lag.intValue()); 116 } 117 } 118 return message; 119 } 120 121 public Message send(Client from, Message message) 122 { 123 return message; 124 } 125 126 public Message sendMeta(Client from, Message message) 127 { 128 Message associated=message.getAssociated(); 129 if (associated != null) 130 { 131 Map<String,Object> extIn=associated.getExt(false); 132 133 if (extIn != null) 134 { 135 Map<String,Object> sync=(Map<String,Object>)extIn.get("timesync"); 136 if (sync != null) 137 { 138 final long tc=((Number)sync.get("tc")).longValue(); 139 final long ts=((Number)sync.get("ts")).longValue(); 140 141 Number lag=(Number)sync.get("l"); 142 if (lag == null) 143 { 144 // old style timesync 145 Map<String,Object> extOut=(Map<String,Object>)message.getExt(true); 146 JSON.Literal timesync=new JSON.Literal("{\"tc\":" + tc + ",\"ts\":" + ts + ",\"p\":" + (System.currentTimeMillis() - ts) + "}"); 147 extOut.put("timesync",timesync); 148 } 149 else 150 { 151 final int l=lag.intValue(); 152 final Number offset = (Number)sync.get("o"); // May return null if is NaN 153 final int o=offset == null ? 0 : offset.intValue(); 154 final int a=(int)((tc + o + l) - ts); 155 156 // is a OK ? 157 if (l == 0 || a >= _accuracyTarget || a <= -_accuracyTarget) 158 { 159 Map<String,Object> extOut=(Map<String,Object>)message.getExt(true); 160 JSON.Literal timesync=new JSON.Literal("{\"tc\":" + tc + ",\"ts\":" + ts + ",\"p\":" + (System.currentTimeMillis() - ts) 161 + ",\"a\":" + a + "}"); 162 163 extOut.put("timesync",timesync); 164 } 165 } 166 } 167 } 168 } 169 return message; 170 } 171 }