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 }