// processFrame handles the initial processing of the given // frame, before passing it on to the relevant helper func, // if necessary. The returned boolean indicates whether the // connection is closing. func (c *Conn) processFrame(frame common.Frame) bool { switch frame := frame.(type) { case *frames.SYN_STREAM: if c.server == nil { c.handlePush(frame) } else { c.handleRequest(frame) } case *frames.SYN_STREAMV3_1: f3 := new(frames.SYN_STREAM) f3.Flags = frame.Flags f3.StreamID = frame.StreamID f3.AssocStreamID = frame.AssocStreamID f3.Priority = frame.Priority f3.Slot = 0 f3.Header = frame.Header if c.server == nil { c.handlePush(f3) } else { c.handleRequest(f3) } case *frames.SYN_REPLY: c.handleSynReply(frame) case *frames.RST_STREAM: if frame.Status.IsFatal() { code := frame.Status.String() log.Printf("Warning: Received %s on stream %d. Closing connection.\n", code, frame.StreamID) c.shutdownError = frame c.Close() return true } c.handleRstStream(frame) case *frames.SETTINGS: for _, setting := range frame.Settings { c.receivedSettings[setting.ID] = setting switch setting.ID { case common.SETTINGS_INITIAL_WINDOW_SIZE: c.initialWindowSizeLock.Lock() initial := int64(c.initialWindowSize) current := c.connectionWindowSize inbound := int64(setting.Value) if initial != inbound { if initial > inbound { c.connectionWindowSize = inbound - (initial - current) } else { c.connectionWindowSize += (inbound - initial) } c.initialWindowSize = setting.Value } c.initialWindowSizeLock.Unlock() case common.SETTINGS_MAX_CONCURRENT_STREAMS: if c.server == nil { c.requestStreamLimit.SetLimit(setting.Value) } else { c.pushStreamLimit.SetLimit(setting.Value) } } } case *frames.PING: // Check whether Ping ID is a response. c.nextPingIDLock.Lock() next := c.nextPingID c.nextPingIDLock.Unlock() if frame.PingID&1 == next&1 { c.pingsLock.Lock() if c.check(c.pings[frame.PingID] == nil, "Ignored unrequested PING %d", frame.PingID) { c.pingsLock.Unlock() return false } c.pings[frame.PingID] <- true close(c.pings[frame.PingID]) delete(c.pings, frame.PingID) c.pingsLock.Unlock() } else { debug.Println("Received PING. Replying...") c.output[0] <- frame } case *frames.GOAWAY: lastProcessed := frame.LastGoodStreamID c.streamsLock.Lock() for streamID, stream := range c.streams { if streamID&1 == c.oddity && streamID > lastProcessed { // Stream is locally-sent and has not been processed. // TODO: Inform the server that the push has not been successful. stream.Close() } } c.streamsLock.Unlock() c.shutdownError = frame c.goawayLock.Lock() c.goawayReceived = true c.goawayLock.Unlock() case *frames.HEADERS: c.handleHeaders(frame) case *frames.WINDOW_UPDATE: c.handleWindowUpdate(frame) case *frames.CREDENTIAL: if c.Subversion > 0 { return false } if c.server == nil || c.certificates == nil { log.Println("Ignored unexpected CREDENTIAL.") return false } if frame.Slot >= c.vectorIndex { setting := new(frames.SETTINGS) setting.Settings = common.Settings{ common.SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE: &common.Setting{ ID: common.SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE, Value: uint32(frame.Slot + 4), }, } c.output[0] <- setting c.vectorIndex += 4 } c.certificates[frame.Slot] = frame.Certificates case *frames.DATA: if c.Subversion > 0 { // The transfer window shouldn't already be negative. if c.connectionWindowSizeThere < 0 { c._GOAWAY(common.GOAWAY_FLOW_CONTROL_ERROR) return false } c.connectionWindowSizeThere -= int64(len(frame.Data)) c.flowControlLock.Lock() f := c.flowControl c.flowControlLock.Unlock() delta := f.ReceiveData(0, c.initialWindowSizeThere, c.connectionWindowSizeThere) if delta != 0 { grow := new(frames.WINDOW_UPDATE) grow.StreamID = 0 grow.DeltaWindowSize = delta c.output[0] <- grow c.connectionWindowSizeThere += int64(grow.DeltaWindowSize) } } if c.server == nil { c.handleServerData(frame) } else { c.handleClientData(frame) } default: log.Println(fmt.Sprintf("Ignored unexpected frame type %T", frame)) } return false }
// NewConn produces an initialised spdy3 connection. func NewConn(conn net.Conn, server *http.Server, subversion int) *Conn { out := new(Conn) // Common ground. out.remoteAddr = conn.RemoteAddr().String() out.server = server out.conn = conn out.buf = bufio.NewReader(conn) if tlsConn, ok := conn.(*tls.Conn); ok { out.tlsState = new(tls.ConnectionState) *out.tlsState = tlsConn.ConnectionState() } out.streams = make(map[common.StreamID]common.Stream) out.output[0] = make(chan common.Frame) out.output[1] = make(chan common.Frame) out.output[2] = make(chan common.Frame) out.output[3] = make(chan common.Frame) out.output[4] = make(chan common.Frame) out.output[5] = make(chan common.Frame) out.output[6] = make(chan common.Frame) out.output[7] = make(chan common.Frame) out.pings = make(map[uint32]chan<- bool) out.compressor = common.NewCompressor(3) out.decompressor = common.NewDecompressor(3) out.receivedSettings = make(common.Settings) out.lastPushStreamID = 0 out.lastRequestStreamID = 0 out.stop = make(chan bool) out.Subversion = subversion // Server/client specific. if server != nil { // servers out.nextPingID = 2 out.oddity = 0 out.initialWindowSize = common.DEFAULT_INITIAL_WINDOW_SIZE out.requestStreamLimit = common.NewStreamLimit(common.DEFAULT_STREAM_LIMIT) out.pushStreamLimit = common.NewStreamLimit(common.NO_STREAM_LIMIT) out.vectorIndex = 8 out.init = func() { // Initialise the connection by sending the connection settings. settings := new(frames.SETTINGS) settings.Settings = defaultServerSettings(common.DEFAULT_STREAM_LIMIT) out.output[0] <- settings } if d := server.ReadTimeout; d != 0 { out.SetReadTimeout(d) } if d := server.WriteTimeout; d != 0 { out.SetWriteTimeout(d) } out.flowControl = DefaultFlowControl(common.DEFAULT_INITIAL_WINDOW_SIZE) out.pushedResources = make(map[common.Stream]map[string]struct{}) if subversion == 0 { out.certificates = make(map[uint16][]*x509.Certificate, 8) if out.tlsState != nil && out.tlsState.PeerCertificates != nil { out.certificates[1] = out.tlsState.PeerCertificates } } else if subversion == 1 { out.connectionWindowSize = common.DEFAULT_INITIAL_WINDOW_SIZE } } else { // clients out.nextPingID = 1 out.oddity = 1 out.initialWindowSize = common.DEFAULT_INITIAL_CLIENT_WINDOW_SIZE out.requestStreamLimit = common.NewStreamLimit(common.NO_STREAM_LIMIT) out.pushStreamLimit = common.NewStreamLimit(common.DEFAULT_STREAM_LIMIT) out.pushRequests = make(map[common.StreamID]*http.Request) out.init = func() { // Initialise the connection by sending the connection settings. settings := new(frames.SETTINGS) settings.Settings = defaultClientSettings(common.DEFAULT_STREAM_LIMIT) out.output[0] <- settings } out.flowControl = DefaultFlowControl(common.DEFAULT_INITIAL_CLIENT_WINDOW_SIZE) if subversion == 1 { out.connectionWindowSize = common.DEFAULT_INITIAL_CLIENT_WINDOW_SIZE } } if subversion == 1 { out.initialWindowSizeThere = out.flowControl.InitialWindowSize() out.connectionWindowSizeThere = int64(out.initialWindowSizeThere) } return out }