xmpp.go
changeset 72 53f15893a1a7
parent 67 e8ad85bb6608
child 74 e619e18dcec3
child 98 c9cc4eda6dce
equal deleted inserted replaced
71:578c2a83dc18 72:53f15893a1a7
    21 	// Version of RFC 3920 that we implement.
    21 	// Version of RFC 3920 that we implement.
    22 	Version = "1.0"
    22 	Version = "1.0"
    23 
    23 
    24 	// Various XML namespaces.
    24 	// Various XML namespaces.
    25 	NsStreams = "urn:ietf:params:xml:ns:xmpp-streams"
    25 	NsStreams = "urn:ietf:params:xml:ns:xmpp-streams"
    26 	NsStream = "http://etherx.jabber.org/streams"
    26 	NsStream  = "http://etherx.jabber.org/streams"
    27 	NsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
    27 	NsTLS     = "urn:ietf:params:xml:ns:xmpp-tls"
    28 	NsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
    28 	NsSASL    = "urn:ietf:params:xml:ns:xmpp-sasl"
    29 	NsBind = "urn:ietf:params:xml:ns:xmpp-bind"
    29 	NsBind    = "urn:ietf:params:xml:ns:xmpp-bind"
    30 	NsSession = "urn:ietf:params:xml:ns:xmpp-session"
    30 	NsSession = "urn:ietf:params:xml:ns:xmpp-session"
    31 	NsRoster = "jabber:iq:roster"
    31 	NsRoster  = "jabber:iq:roster"
    32 
    32 
    33 	// DNS SRV names
    33 	// DNS SRV names
    34 	serverSrv = "xmpp-server"
    34 	serverSrv = "xmpp-server"
    35 	clientSrv = "xmpp-client"
    35 	clientSrv = "xmpp-client"
    36 )
    36 )
    61 	}(idCh)
    61 	}(idCh)
    62 }
    62 }
    63 
    63 
    64 // Extensions can add stanza filters and/or new XML element types.
    64 // Extensions can add stanza filters and/or new XML element types.
    65 type Extension struct {
    65 type Extension struct {
    66 	StanzaHandlers map[string] func(*xml.Name) interface{}
    66 	StanzaHandlers map[string]func(*xml.Name) interface{}
    67 	Start func(*Client)
    67 	Start          func(*Client)
    68 }
    68 }
    69 
    69 
    70 // The client in a client-server XMPP connection.
    70 // The client in a client-server XMPP connection.
    71 type Client struct {
    71 type Client struct {
    72 	// This client's unique ID. It's unique within the context of
    72 	// This client's unique ID. It's unique within the context of
    73 	// this process, so if multiple Client objects exist, each
    73 	// this process, so if multiple Client objects exist, each
    74 	// will be distinguishable by its Uid.
    74 	// will be distinguishable by its Uid.
    75 	Uid string
    75 	Uid string
    76 	// This client's JID. This will be updated asynchronously by
    76 	// This client's JID. This will be updated asynchronously by
    77 	// the time StartSession() returns.
    77 	// the time StartSession() returns.
    78 	Jid JID
    78 	Jid          JID
    79 	password string
    79 	password     string
    80 	socket net.Conn
    80 	socket       net.Conn
    81 	socketSync sync.WaitGroup
    81 	socketSync   sync.WaitGroup
    82 	saslExpected string
    82 	saslExpected string
    83 	authDone bool
    83 	authDone     bool
    84 	handlers chan *stanzaHandler
    84 	handlers     chan *stanzaHandler
    85 	inputControl chan int
    85 	inputControl chan int
    86 	// Incoming XMPP stanzas from the server will be published on
    86 	// Incoming XMPP stanzas from the server will be published on
    87 	// this channel. Information which is only used by this
    87 	// this channel. Information which is only used by this
    88 	// library to set up the XMPP stream will not appear here.
    88 	// library to set up the XMPP stream will not appear here.
    89 	In <-chan Stanza
    89 	In <-chan Stanza
    90 	// Outgoing XMPP stanzas to the server should be sent to this
    90 	// Outgoing XMPP stanzas to the server should be sent to this
    91 	// channel.
    91 	// channel.
    92 	Out chan<- Stanza
    92 	Out    chan<- Stanza
    93 	xmlOut chan<- interface{}
    93 	xmlOut chan<- interface{}
    94 	// Features advertised by the remote. This will be updated
    94 	// Features advertised by the remote. This will be updated
    95 	// asynchronously as new features are received throughout the
    95 	// asynchronously as new features are received throughout the
    96 	// connection process. It should not be updated once
    96 	// connection process. It should not be updated once
    97 	// StartSession() returns.
    97 	// StartSession() returns.
    98 	Features *Features
    98 	Features  *Features
    99 	filterOut chan<- <-chan Stanza
    99 	filterOut chan<- <-chan Stanza
   100 	filterIn <-chan <-chan Stanza
   100 	filterIn  <-chan <-chan Stanza
   101 }
   101 }
   102 
   102 
   103 // Connect to the appropriate server and authenticate as the given JID
   103 // Connect to the appropriate server and authenticate as the given JID
   104 // 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
   105 // connection has been established, but before XMPP stream negotiation
   105 // connection has been established, but before XMPP stream negotiation
   106 // has completed. The negotiation will occur asynchronously, and any
   106 // has completed. The negotiation will occur asynchronously, and any
   107 // send operation to Client.Out will block until negotiation (resource
   107 // send operation to Client.Out will block until negotiation (resource
   108 // binding) is complete.
   108 // binding) is complete.
   109 func NewClient(jid *JID, password string, exts []Extension) (*Client,
   109 func NewClient(jid *JID, password string, exts []Extension) (*Client,
   110 	os.Error) {
   110 os.Error) {
   111 	// Include the mandatory extensions.
   111 	// Include the mandatory extensions.
   112 	exts = append(exts, rosterExt)
   112 	exts = append(exts, rosterExt)
   113 	exts = append(exts, bindExt)
   113 	exts = append(exts, bindExt)
   114 
   114 
   115 	// Resolve the domain in the JID.
   115 	// Resolve the domain in the JID.
   138 	if tcp == nil {
   138 	if tcp == nil {
   139 		return nil, err
   139 		return nil, err
   140 	}
   140 	}
   141 
   141 
   142 	cl := new(Client)
   142 	cl := new(Client)
   143 	cl.Uid = <- Id
   143 	cl.Uid = <-Id
   144 	cl.password = password
   144 	cl.password = password
   145 	cl.Jid = *jid
   145 	cl.Jid = *jid
   146 	cl.socket = tcp
   146 	cl.socket = tcp
   147 	cl.handlers = make(chan *stanzaHandler, 100)
   147 	cl.handlers = make(chan *stanzaHandler, 100)
   148 	cl.inputControl = make(chan int)
   148 	cl.inputControl = make(chan int)
   149 
   149 
   150 	extStanza := make(map[string] func(*xml.Name) interface{})
   150 	extStanza := make(map[string]func(*xml.Name) interface{})
   151 	for _, ext := range(exts) {
   151 	for _, ext := range exts {
   152 		for k, v := range(ext.StanzaHandlers) {
   152 		for k, v := range ext.StanzaHandlers {
   153 			extStanza[k] = v
   153 			extStanza[k] = v
   154 		}
   154 		}
   155 	}
   155 	}
   156 
   156 
   157 	// Start the transport handler, initially unencrypted.
   157 	// Start the transport handler, initially unencrypted.
   171 	// app sees.
   171 	// app sees.
   172 	clIn := cl.startFilter(stIn)
   172 	clIn := cl.startFilter(stIn)
   173 	cl.In = clIn
   173 	cl.In = clIn
   174 
   174 
   175 	// Add filters for our extensions.
   175 	// Add filters for our extensions.
   176 	for _, ext := range(exts) {
   176 	for _, ext := range exts {
   177 		ext.Start(cl)
   177 		ext.Start(cl)
   178 	}
   178 	}
   179 
   179 
   180 	// Initial handshake.
   180 	// Initial handshake.
   181 	hsOut := &stream{To: jid.Domain, Version: Version}
   181 	hsOut := &stream{To: jid.Domain, Version: Version}
   191 	go cl.writeTransport(outr)
   191 	go cl.writeTransport(outr)
   192 	return inr, outw
   192 	return inr, outw
   193 }
   193 }
   194 
   194 
   195 func startXmlReader(r io.Reader,
   195 func startXmlReader(r io.Reader,
   196 	extStanza map[string] func(*xml.Name) interface{}) <-chan interface{} {
   196 extStanza map[string]func(*xml.Name) interface{}) <-chan interface{} {
   197 	ch := make(chan interface{})
   197 	ch := make(chan interface{})
   198 	go readXml(r, ch, extStanza)
   198 	go readXml(r, ch, extStanza)
   199 	return ch
   199 	return ch
   200 }
   200 }
   201 
   201 
   229 	return cliIn
   229 	return cliIn
   230 }
   230 }
   231 
   231 
   232 func tee(r io.Reader, w io.Writer, prefix string) {
   232 func tee(r io.Reader, w io.Writer, prefix string) {
   233 	defer func(w io.Writer) {
   233 	defer func(w io.Writer) {
   234 		if c, ok := w.(io.Closer) ; ok {
   234 		if c, ok := w.(io.Closer); ok {
   235 			c.Close()
   235 			c.Close()
   236 		}
   236 		}
   237 	}(w)
   237 	}(w)
   238 
   238 
   239 	buf := bytes.NewBuffer([]uint8(prefix))
   239 	buf := bytes.NewBuffer([]uint8(prefix))
   270 // immediately after creating the Client in order to start the
   270 // immediately after creating the Client in order to start the
   271 // session, retrieve the roster, and broadcast an initial
   271 // session, retrieve the roster, and broadcast an initial
   272 // presence. The presence can be as simple as a newly-initialized
   272 // presence. The presence can be as simple as a newly-initialized
   273 // Presence struct.  See RFC 3921, Section 3.
   273 // Presence struct.  See RFC 3921, Section 3.
   274 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error {
   274 func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error {
   275 	id := <- Id
   275 	id := <-Id
   276 	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested:
   276 	iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}
   277 		[]interface{}{ Generic{XMLName: xml.Name{Space:
       
   278 					NsSession, Local: "session"}}}}
       
   279 	ch := make(chan os.Error)
   277 	ch := make(chan os.Error)
   280 	f := func(st Stanza) bool {
   278 	f := func(st Stanza) bool {
   281 		if st.GetType() == "error" {
   279 		if st.GetType() == "error" {
   282 			if Log != nil {
   280 			if Log != nil {
   283 				Log.Err(fmt.Sprintf("Can't start session: %v",
   281 				Log.Err(fmt.Sprintf("Can't start session: %v",
   291 	}
   289 	}
   292 	cl.HandleStanza(id, f)
   290 	cl.HandleStanza(id, f)
   293 	cl.Out <- iq
   291 	cl.Out <- iq
   294 
   292 
   295 	// Now wait until the callback is called.
   293 	// Now wait until the callback is called.
   296 	if err := <- ch ; err != nil {
   294 	if err := <-ch; err != nil {
   297 		return err
   295 		return err
   298 	}
   296 	}
   299 	if getRoster {
   297 	if getRoster {
   300 		err := fetchRoster(cl)
   298 		err := fetchRoster(cl)
   301 		if err != nil {
   299 		if err != nil {
   313 // filter's output channel is given to this function, and it returns a
   311 // filter's output channel is given to this function, and it returns a
   314 // new input channel which the filter should read from. When its input
   312 // new input channel which the filter should read from. When its input
   315 // channel closes, the filter should close its output channel.
   313 // channel closes, the filter should close its output channel.
   316 func (cl *Client) AddFilter(out <-chan Stanza) <-chan Stanza {
   314 func (cl *Client) AddFilter(out <-chan Stanza) <-chan Stanza {
   317 	cl.filterOut <- out
   315 	cl.filterOut <- out
   318 	return <- cl.filterIn
   316 	return <-cl.filterIn
   319 }
   317 }