View Javadoc

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 }