xmpp.go
changeset 57 e6cb3f049137
parent 51 1af366d10d32
child 59 be6815a9653a
equal deleted inserted replaced
56:74686b8c9146 57:e6cb3f049137
    42 	// syslog.Writer, so the app can control how much time we
    42 	// syslog.Writer, so the app can control how much time we
    43 	// spend generating log messages, as well as where they go.
    43 	// spend generating log messages, as well as where they go.
    44 	debug = false
    44 	debug = false
    45 )
    45 )
    46 
    46 
       
    47 // This channel may be used as a convenient way to generate a unique
       
    48 // id for an iq, message, or presence stanza.
       
    49 var Id <-chan string
       
    50 
       
    51 func init() {
       
    52 	// Start the unique id generator.
       
    53 	idCh := make(chan string)
       
    54 	Id = idCh
       
    55 	go func(ch chan<- string) {
       
    56 		id := int64(1)
       
    57 		for {
       
    58 			str := fmt.Sprintf("id_%d", id)
       
    59 			ch <- str
       
    60 			id++
       
    61 		}
       
    62 	}(idCh)
       
    63 }
       
    64 
    47 // The client in a client-server XMPP connection.
    65 // The client in a client-server XMPP connection.
    48 type Client struct {
    66 type Client struct {
       
    67 	// This client's unique ID. It's unique within the context of
       
    68 	// this process, so if multiple Client objects exist, each
       
    69 	// will be distinguishable by its Uid.
       
    70 	Uid string
    49 	// This client's JID. This will be updated asynchronously by
    71 	// This client's JID. This will be updated asynchronously by
    50 	// the time StartSession() returns.
    72 	// the time StartSession() returns.
    51 	Jid JID
    73 	Jid JID
    52 	password string
    74 	password string
    53 	socket net.Conn
    75 	socket net.Conn
    54 	socketSync sync.WaitGroup
    76 	socketSync sync.WaitGroup
    55 	saslExpected string
    77 	saslExpected string
    56 	authDone bool
    78 	authDone bool
    57 	handlers chan *stanzaHandler
    79 	handlers chan *stanzaHandler
    58 	inputControl chan int
    80 	inputControl chan int
    59 	// This channel may be used as a convenient way to generate a
       
    60 	// unique id for an iq, message, or presence stanza.
       
    61 	Id <-chan string
       
    62 	// Incoming XMPP stanzas from the server will be published on
    81 	// Incoming XMPP stanzas from the server will be published on
    63 	// this channel. Information which is only used by this
    82 	// this channel. Information which is only used by this
    64 	// library to set up the XMPP stream will not appear here.
    83 	// library to set up the XMPP stream will not appear here.
    65 	In <-chan Stanza
    84 	In <-chan Stanza
    66 	// Outgoing XMPP stanzas to the server should be sent to this
    85 	// Outgoing XMPP stanzas to the server should be sent to this
    70 	// Features advertised by the remote. This will be updated
    89 	// Features advertised by the remote. This will be updated
    71 	// asynchronously as new features are received throughout the
    90 	// asynchronously as new features are received throughout the
    72 	// connection process. It should not be updated once
    91 	// connection process. It should not be updated once
    73 	// StartSession() returns.
    92 	// StartSession() returns.
    74 	Features *Features
    93 	Features *Features
    75 	roster map[string] *RosterItem
       
    76 	filterOut chan<- <-chan Stanza
    94 	filterOut chan<- <-chan Stanza
    77 	filterIn <-chan <-chan Stanza
    95 	filterIn <-chan <-chan Stanza
    78 }
    96 }
    79 var _ io.Closer = &Client{}
    97 var _ io.Closer = &Client{}
       
    98 
       
    99 // BUG(cjyar) Replace extStanza with a generalized extension interface
       
   100 // that handles starting filters, registering extended stanzes, and
       
   101 // anything else an extension has to do.
    80 
   102 
    81 // Connect to the appropriate server and authenticate as the given JID
   103 // Connect to the appropriate server and authenticate as the given JID
    82 // with the given password. This function will return as soon as a TCP
   104 // with the given password. This function will return as soon as a TCP
    83 // connection has been established, but before XMPP stream negotiation
   105 // connection has been established, but before XMPP stream negotiation
    84 // has completed. The negotiation will occur asynchronously, and any
   106 // has completed. The negotiation will occur asynchronously, and any
   112 	if tcp == nil {
   134 	if tcp == nil {
   113 		return nil, err
   135 		return nil, err
   114 	}
   136 	}
   115 
   137 
   116 	cl := new(Client)
   138 	cl := new(Client)
       
   139 	cl.Uid = <- Id
   117 	cl.password = password
   140 	cl.password = password
   118 	cl.Jid = *jid
   141 	cl.Jid = *jid
   119 	cl.socket = tcp
   142 	cl.socket = tcp
   120 	cl.handlers = make(chan *stanzaHandler, 100)
   143 	cl.handlers = make(chan *stanzaHandler, 100)
   121 	cl.inputControl = make(chan int)
   144 	cl.inputControl = make(chan int)
   122 	idCh := make(chan string)
       
   123 	cl.Id = idCh
       
   124 
   145 
   125 	if extStanza == nil {
   146 	if extStanza == nil {
   126 		extStanza = make(map[string] func(*xml.Name) interface{})
   147 		extStanza = make(map[string] func(*xml.Name) interface{})
   127 	}
   148 	}
   128 	extStanza[NsRoster] = newRosterQuery
   149 	extStanza[NsRoster] = newRosterQuery
   129 	extStanza[NsBind] = newBind
   150 	extStanza[NsBind] = newBind
   130 
       
   131 	// Start the unique id generator.
       
   132 	go makeIds(idCh)
       
   133 
   151 
   134 	// Start the transport handler, initially unencrypted.
   152 	// Start the transport handler, initially unencrypted.
   135 	tlsr, tlsw := cl.startTransport()
   153 	tlsr, tlsw := cl.startTransport()
   136 
   154 
   137 	// Start the reader and writers that convert to and from XML.
   155 	// Start the reader and writers that convert to and from XML.
   144 	clOut := cl.startStreamWriter(cl.xmlOut)
   162 	clOut := cl.startStreamWriter(cl.xmlOut)
   145 
   163 
   146 	// Start the manager for the filters that can modify what the
   164 	// Start the manager for the filters that can modify what the
   147 	// app sees.
   165 	// app sees.
   148 	clIn := cl.startFilter(stIn)
   166 	clIn := cl.startFilter(stIn)
   149 	cl.startRosterFilter()
   167 	startRosterFilter(cl)
   150 
   168 
   151 	// Initial handshake.
   169 	// Initial handshake.
   152 	hsOut := &stream{To: jid.Domain, Version: Version}
   170 	hsOut := &stream{To: jid.Domain, Version: Version}
   153 	cl.xmlOut <- hsOut
   171 	cl.xmlOut <- hsOut
   154 
   172 
   257 			f2(ch)
   275 			f2(ch)
   258 		}
   276 		}
   259 	}
   277 	}
   260 }
   278 }
   261 
   279 
   262 func makeIds(ch chan<- string) {
       
   263 	id := int64(1)
       
   264 	for {
       
   265 		str := fmt.Sprintf("id_%d", id)
       
   266 		ch <- str
       
   267 		id++
       
   268 	}
       
   269 }
       
   270 
       
   271 // bindDone is called when we've finished resource binding (and all
   280 // bindDone is called when we've finished resource binding (and all
   272 // the negotiations that precede it). Now we can start accepting
   281 // the negotiations that precede it). Now we can start accepting
   273 // traffic from the app.
   282 // traffic from the app.
   274 func (cl *Client) bindDone() {
   283 func (cl *Client) bindDone() {
   275 	cl.inputControl <- 1
   284 	cl.inputControl <- 1
   279 // immediately after creating the Client in order to start the
   288 // immediately after creating the Client in order to start the
   280 // session, retrieve the roster, and broadcast an initial
   289 // session, retrieve the roster, and broadcast an initial
   281 // presence. The presence can be as simple as a newly-initialized
   290 // presence. The presence can be as simple as a newly-initialized
   282 // Presence struct.  See RFC 3921, Section 3.
   291 // Presence struct.  See RFC 3921, Section 3.
   283 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error {
   292 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error {
   284 	id := <- cl.Id
   293 	id := <- Id
   285 	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any:
   294 	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Any:
   286 		&Generic{XMLName: xml.Name{Space: NsSession, Local:
   295 		&Generic{XMLName: xml.Name{Space: NsSession, Local:
   287 				"session"}}}
   296 				"session"}}}
   288 	ch := make(chan os.Error)
   297 	ch := make(chan os.Error)
   289 	f := func(st Stanza) bool {
   298 	f := func(st Stanza) bool {
   301 	// Now wait until the callback is called.
   310 	// Now wait until the callback is called.
   302 	if err := <- ch ; err != nil {
   311 	if err := <- ch ; err != nil {
   303 		return err
   312 		return err
   304 	}
   313 	}
   305 	if getRoster {
   314 	if getRoster {
   306 		err := cl.fetchRoster()
   315 		err := fetchRoster(cl)
   307 		if err != nil {
   316 		if err != nil {
   308 			return err
   317 			return err
   309 		}
   318 		}
   310 	}
   319 	}
   311 	if pr != nil {
   320 	if pr != nil {