$Id: ts3.txt 33 2005-10-02 20:50:00Z knight $
                       Protocol changes for +TSora
                       ---------------------------

Note: 

The protocols described here implement TimeStamps on IRC channels and 
nicks. The idea of IRC TimeStamps was started on Undernet, and first 
implemented by Run <carlo@runaway.xs4all.nl>. The protocols used here 
are not exactly the same as the ones used on Undernet; the nick-kill 
handling is very similar and must be credited to Run, while the 
"TimeStamped channel description" protocol is quite different.

TSora servers keep track of which version of the TS protocol (if any) 
their neighboring servers are using, and take it into account when 
sending messages to them. This allows for seamless integration of TS 
servers into a non-TS net, and for upgrades of the protocol.

Each server knows which is the lowest and the highest version of the
TS protocol it can interact with; currently both of these are set to 1:

#define TS_CURRENT 1		/* the highest TS ver we can do */
#define TS_MIN 1		/* the lowest TS ver we can do  */

Timings and TS versions:
========================

. Keep a 'delta' value to be added to the result of all calls to time(),
  initially 0.

. Send a second argument to the PASS command, ending in the 'TS' string.

. Send a

  SVINFO <TS_CURRENT> <TS_MIN> <STANDALONE> :<UTC-TIME>
  
  just after "SERVER", where <STANDALONE> is 1 if we're connected to 
  more TSora servers, and 0 if not, and <UTC-TIME> is our idea of the 
  current UTC time, fixed with the delta.

. When we receive a "SVINFO <x> <y> <z> :<t>" line from a connecting 
  server, we ignore it if TS_CURRENT<y or x<TS_MIN, otherwise we
  set a flag remembering that that server is TS-aware, remember the TS 
  version to use with it (min(TS_CURRENT, x)). Additionally, if this is 
  our first connected TS server, we set our delta to t-<OUR_UTC> if
  z==0, and to (t-<OUR_UTC>)/2 if z!=0. The SVINFO data is kept around
  until the server has effectively registered with SERVER, and used
  *after* sending our own SVINFO to that server.

Explanations:

  Servers will always know which of their directly-linked servers can do 
  TS, and will use the TS protocol only with servers that do understand 
  it. This makes it possible to switch to full TS in just one 
  code-replacement step, without incompatibilities.

  As long as not all servers are TS-aware, the net will be divided into 
  "zones" of linked TS-aware servers. Channel modes will be kept 
  synchronized at least within the zone in which the channel was 
  created, and nick collisions between servers in the same zone will 
  result in only one client being killed.

  Time synchronization ensures that servers have the same idea of the 
  current time, and achieves this purpose as long as TS servers are 
  introduced one by one within the same 'zone'. The merging of two zones 
  cannot synchronize them completely, but it is to be expected that 
  within each zone the effective time will be very close to the real 
  time. 

  By sending TSINFO after SERVER rather than before, we avoid the extra 
  lag created by the identd check on the server. To be able to send 
  immediately a connect burst of either type (TS or not), we need to 
  know before that if the server does TS or not, so we send that 
  information with PASS as an extra argument. And to avoid being 
  incompatible with 2.9 servers, which check that this second argument 
  begins with "2.9", we check that it *ends* with "TS".

  The current time is only used when setting a TS on a new channel or 
  nick, and once such a TS is set, it is never modified because of 
  synchronization, as it is much more important that the TS for a 
  channel or nick stays the same across all servers than that it is 
  accurate to the second.

  Note that Undernet's 2.8.x servers have no time synchronization at 
  all, and have had no problems because of it - all of this is more to 
  catch the occasional server with a way-off clock than anything.

NICK handling patches (anti-nick-collide + shorter connect burst):
==================================================================

. For each nick, store a TS value = the TS value received if any, or our 
  UTC+delta at the time we first heard of the nick. TS's are propagated 
  to TS-aware servers whenever sending a NICK command.

. Nick changes reset the TS to the current time.

. When sending a connect burst to another TS server, replace the 
  NICK/USER pair with only one NICK command containing the nick, the 
  hopcount, the TS, the umode, and all the USER information.

  The format for a full NICK line is:
  NICK <nick> <hops> <TS> <umode> <user> <host> <server> :<ircname>

  The umode is a + followed by any applying usermodes.

  The format for a nick-change NICK line is:
  :<oldnick> NICK <newnick> :<TS>

. When a NICK is received from a TS server, that conflicts with an 
  existing nick:
  + if the userhosts differ or one is not known:
    * if the timestamps are equal, kill ours and the old one if it
      was a nick change
    * if the incoming timestamp is older than ours, kill ours and 
      propagate the new one
    * if the incoming timestamp is younger, ignore the line, but kill 
      the old nick if it was a nick change
  + if the userhosts are the same:
    * if the timestamps are equal, kill ours and the old one if it
      was a nick change
    * if the incoming timestamp is younger, kill ours and propagate
      the new one
    * if the incoming timestamp is older, ignore the line but kill
      the old nick if it was a nick change

. When a NICK is received from a non-TS server that conflicts with
  an existing nick, kill both.

. Do not send "Fake Prefix" kills in response to lines coming from TS 
  servers; the sanitization works anyway, and this allows the "newer
  nick overruled" case to work.

Explanations:

  The modified nick-introduction syntax allows for a slightly shorter 
  connect-burst, and most importantly lets the server compare 
  user@host's when determining which nick to kill:  if the user@host
  is the same, then the older nick must be killed rather than the
  newer.

  When talking to a non-TS server, we need to behave exactly like one
  because it expects us to. When talkign to a TS server, we don't kill
  the nicks it's introducing, as we know it'll be smart enough to do it
  itself when seeing our own introduced nick.

  When we see a nick arriving from a non-TS server, it won't have a TS, 
  but it's safe enough to give it the current time rather than keeping 
  it 0; such TS's won't be the same all across the network (as long as 
  there is more than one TS zone), and when there's a collision, the TS 
  used will be the one in the zone the collision occurs in.

  Also, it is important to note that by the time a server sees (and 
  chooses to ignore) a nick introduction, the introducing server has 
  also had the time to put umode changes for that nick on its queue, so 
  we must ignore them too... so we need to ignore fake-prefix lines 
  rather than sending kills for them. This is safe enough, as the rest 
  of the protocol ensures that they'll get killed anyway (and the 
  Undernet does it too, so it's been more than enough tested). Just for 
  an extra bit of compatibility, we still kill fake prefixes coming from 
  non-TS servers.

  This part of the TS protocol is almost exactly the same as the 
  Undernet's .anc (anti-nick-collide) patches, except that Undernet 
  servers don't add usermodes to the NICK line.

TimeStamped channel descriptions (avoiding hacked ops and desynchs):
====================================================================

. For each channel, keep a timestamp, set to the current time when the
  channel is created by a client on the local server, or to the received 
  value if the channel has been propagated from a TS server, or to 0 
  otherwise. This value will have the semantics of "the time of creation 
  of the current ops on the channel", and 0 will mean that the channel 
  is in non-TS mode.

  A new server protocol command is introduced, SJOIN, which introduces 
  a full channel description: a timestamp, all the modes (except bans),
  and the list of channel members with their ops and voices. This 
  command will be used instead of JOIN and of (most) MODEs both in 
  connect bursts and when propagating channel creations among TS 
  servers. SJOIN will never be accepted from or sent to users.

  The syntax for the command is:

  SJOIN <TS> #<channel> <modes> :[@][+]<nick_1> ...  [@][+]<nick_n>

  The fields have the following meanings:

  * <TS> is the timestamp for the channel

  * <modes> is the list of global channel modes, starting with a +
    and a letter for each of the active modes (spmntkil), followed
    by an argument for +l if there is a limit, and an argument for
    +k if there's a key (in the same order they were mentioned in
    the string of letters).

    A channel with no modes will have a "+" in that field.

    A special value of "0" means that the server does not specify the 
    modes, and will be used when more than one SJOIN line is needed
    to completely describe a channel, or when propagating a SJOIN
    the modes of which were rejected.

  * Each nick is preceded by a "@" if the user has ops, and a "+" if
    the user has a voice. For mode +ov, both flags are used.

  SJOINs will be propagated (when appropriate) to neighboring TS 
  servers, and converted to JOINs and MODEs for neighboring non-TS 
  servers.
  
  To propagate channels for which not all users fit in one 
  SJOIN line, several SJOINs will be sent consecutively, only the first
  one including actual information in the <mode> field.
  
  An extra ad-hoc restriction is imposed on SJOIN messages, to simplify 
  processing: if a channel has ops, then the first <nick> of the first 
  SJOIN sent to propagate that channel must be one of the ops.

  Servers will never attempt to reconstruct a SJOIN from JOIN/MODE 
  information being received at the moment from other servers.

. For each user on a channel, keep an extra flag (like ops and voice)
  that is set when the user has received channel ops from another 
  server (in a SJOIN channel description), which we rejected (ignored). 
  Mode changes (but NOT kicks) coming from a TS server and from someone 
  with this flag set will be ignored. The flag will be reset when the 
  user gets ops from another user or server.

. On deops done by non-local users, coming from TS servers, on channels 
  with a non-zero TS, do not check that the user has ops but check that 
  their 'deopped' flag is not set. For kicks coming from a TS server, do 
  not check either. This will avoid desynchs, and 'bad' modechanges are 
  avoided anyway. Other mode changes will still only be taken into 
  account and propagated when done by users that are seen as having ops.

. When a MODE change that ops someone is received from a server for a 
  channel, that channel's TS is set to 0, and the mode change is 
  propagated. 

. When a SJOIN is received for a channel, deal with it in this way:
  * received-TS = 0: 
    + if we have ops or the SJOIN doesn't op anyone, SJOIN propagated
      with our own TS.
    + otherwise, TS set to 0 and SJOIN propagated with 0.
  * received-TS > 0, own-TS = 0: 
    + if the SJOIN ops someone or we don't have ops, set our TS to the
      received TS and propagate.
    + otherwise, propagate with TS = 0.
  * received-TS = own-TS: propagate.
  * received-TS < own-TS: 
    + if the SJOIN ops someone, remove *all* modes (except bans) from 
      the channel and propagate these mode changes to all neighboring
      non-TS servers, and copy the received TS and propagate the SJOIN.
    + if the SJOIN does not op anyone and we have ops, propagate
      with our own TS.
    + otherwise, copy the received TS and propagate the SJOIN.
  * received-TS > own-TS: 
    + if the SJOIN does not introduce any ops, process and propagate
      with our own TS.
    + if we have ops: for each person the mode change would op, set the 
      'deopped' flag; process all the JOINs ignoring the '@' and '+' 
      flags; propagate without the flags and with our TS.
    + if we don't have ops: set our TS to the received one, propagate
      with the flags.

Explanations:

  This part of the protocol is the one that is most different (and 
  incompatible) with the Undernet's: we never timestamp MODE changes, 
  but instead we introduce the concept of time-stamped channel 
  descriptions. This way each server can determine, based on its state 
  and the received description, what the correct modes for a channel 
  are, and deop its own users if necessary. With this protocol, there is 
  *never* the need to reverse and bounce back a mode change. This is
  both faster and more bandwith-effective.

  The end goal is to have a protocol will eventually protect channels 
  against hacked ops, while minimizing the impact on a mixed-server net. 
  In order to do this, whenever there is a conflict between a TS server 
  and a non-TS one, the non-TS one's idea of the whole situation 
  prevails. This means that channels will only have a TS when they have 
  been created on a TS-aware server, and will lose it whenever a server 
  op comes from a non-TS server. Also, at most one 'zone' will have a TS 
  for any given channel at any given time, ensuring that there won't be 
  any deops when zones are merged. However, when TS zones are merged, if 
  the side that has a TS also has ops, then the TS is kept across the 
  whole new zone. Effective protection will only be ensured once all 
  servers run TS patches and channels have been re-created, as there is 
  no way servers can assign a TS to a channel they are not creating 
  (like they do with nicks) without having unwanted deops later.

  The visible effects of this timestamped channel-description protocol 
  are that when a split rejoins, and one side has hacked ops, the other 
  side doesn't see any server mode changes (just like with Undernet's
  TS), but the side that has hacked ops sees:

  * first the first server on the other side deopping and devoicing 
    everyone, and fixing the +spmntkli modes
  * then other users joining, and getting server ops and voices

  The less obvious part of this protocol is its behavior in the case 
  that the younger side of a rejoin has servers that are lagged with 
  each other. In such a situation, a SJOIN that clears all modes and 
  sets the legitimate ones is being propagated from one server, and 
  lagged illegitimate mode changes and kicks are being propagated in the 
  opposite direction. In this case, a kick done by someone who is being 
  deopped by the SJOIN must be taken into account to keep the name list 
  in sync (and since it can only be kicking someone who also was on the 
  younger side), while a deop does not matter (and will be ignored by 
  the first server on the other side), and an opping *needs* to be 
  discareded to avoid hacked ops.

  The main property of timestamped channel descriptions that makes them 
  a very stable protocol even with lag and splits, is that they leave a 
  server in the same final state, independently of the order in which 
  channel descriptions coming from different servers are received. Even 
  when SJOINs and MODEs for the same channel are being propagated in 
  different direction because of several splits rejoining, the final 
  state will be the same, independently of the exact order in which each 
  server received the SJOINs, and will be the same across all the 
  servers in the same zone.
