// ========================================================================
// Copyright 2006 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package org.mortbay.cometd.ext;
import java.util.Map;
import org.cometd.Client;
import org.cometd.Extension;
import org.cometd.Message;
import org.mortbay.cometd.ClientImpl;
import org.mortbay.util.ajax.JSON;
/* ------------------------------------------------------------ */
/**
* Timesync extension (server side). With each handshake or connect, the
* extension sends timestamps within the ext field like:
* <code>{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}</code> where:
* <ul>
* <li>tc is the client timestamp in ms since 1970 of when the message was sent.
* <li>l is the network lag that the client has calculated.
* <li>o is the clock offset that the client has calculated.
* </ul>
*
* <p>
* A cometd server that supports timesync, can respond with an ext field like:
* <code>{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}</code>
* where:
* <ul>
* <li>tc is the client timestamp of when the message was sent,
* <li>ts is the server timestamp of when the message was received
* <li>p is the poll duration in ms - ie the time the server took before sending
* the response.
* <li>a is the measured accuracy of the calculated offset and lag sent by the
* client
* </ul>
* <p>
* The relationship between tc, ts, o & l on the server is given by
* <code>ts=tc+o+l</code> (the time the server received the message is the
* client time plus the offset plus the network lag). Thus the accuracy of the o
* and l settings can be determined with <code>a=tc+o+l-ts</code>.
* </p>
* <p>
* When the client has received the response, it can make a more accurate
* estimate of the lag as <code>l2=(now-tc-p)/2</code> (assuming symmetric lag).
* A new offset can then be calculated with the relationship on the client that
* <code>ts=tc+o2+l2</code>, thus <code>o2=ts-tc-l2</code>.
* </p>
* <p>
* Since the client also receives the a value calculated on the server, it
* should be possible to analyse this and compensate for some asymmetry in the
* lag. But the current client does not do this.
* </p>
*/
public class TimesyncExtension implements Extension
{
private int _accuracyTarget=25;
public TimesyncExtension()
{
}
/* ------------------------------------------------------------ */
/**
* timesync responses are not set if the measured accuracy is less than the
* accuracyTarget.
*
* @return accuracy target in ms (default 25ms)
*/
public int getAccuracyTarget()
{
return _accuracyTarget;
}
/* ------------------------------------------------------------ */
/**
* timesync responses are not set if the measured accuracy is less than the
* accuracyTarget.
*
* @param target
* accuracy target in ms
*/
public void setAccuracyTarget(int target)
{
_accuracyTarget=target;
}
public Message rcv(Client from, Message message)
{
return message;
}
public Message rcvMeta(Client from, Message message)
{
Map<String,Object> ext=message.getExt(false);
if (ext != null)
{
Map<String,Object> sync=(Map<String,Object>)ext.get("timesync");
if (sync != null)
{
sync.put("ts",new Long(System.currentTimeMillis()));
Number lag=(Number)sync.get("l");
if (lag != null && from != null)
((ClientImpl)from).setLag(lag.intValue());
}
}
return message;
}
public Message send(Client from, Message message)
{
return message;
}
public Message sendMeta(Client from, Message message)
{
Message associated=message.getAssociated();
if (associated != null)
{
Map<String,Object> extIn=associated.getExt(false);
if (extIn != null)
{
Map<String,Object> sync=(Map<String,Object>)extIn.get("timesync");
if (sync != null)
{
final long tc=((Number)sync.get("tc")).longValue();
final long ts=((Number)sync.get("ts")).longValue();
Number lag=(Number)sync.get("l");
if (lag == null)
{
// old style timesync
Map<String,Object> extOut=(Map<String,Object>)message.getExt(true);
JSON.Literal timesync=new JSON.Literal("{\"tc\":" + tc + ",\"ts\":" + ts + ",\"p\":" + (System.currentTimeMillis() - ts) + "}");
extOut.put("timesync",timesync);
}
else
{
final int l=lag.intValue();
final Number offset = (Number)sync.get("o"); // May return null if is NaN
final int o=offset == null ? 0 : offset.intValue();
final int a=(int)((tc + o + l) - ts);
// is a OK ?
if (l == 0 || a >= _accuracyTarget || a <= -_accuracyTarget)
{
Map<String,Object> extOut=(Map<String,Object>)message.getExt(true);
JSON.Literal timesync=new JSON.Literal("{\"tc\":" + tc + ",\"ts\":" + ts + ",\"p\":" + (System.currentTimeMillis() - ts)
+ ",\"a\":" + a + "}");
extOut.put("timesync",timesync);
}
}
}
}
}
return message;
}
}