// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package tls import ( "fmt"; "hash"; "io"; ) // writerEnableApplicationData is a message which instructs recordWriter to // start reading and transmitting data from the application data channel. type writerEnableApplicationData struct{} // writerChangeCipherSpec updates the encryption and MAC functions and resets // the sequence count. type writerChangeCipherSpec struct { encryptor encryptor; mac hash.Hash; } // writerSetVersion sets the version number bytes that we included in the // record header for future records. type writerSetVersion struct { major, minor uint8; } // A recordWriter accepts messages from the handshake processor and // application data. It writes them to the outgoing connection and blocks on // writing. It doesn't read from the application data channel until the // handshake processor has signaled that the handshake is complete. type recordWriter struct { writer io.Writer; encryptor encryptor; mac hash.Hash; seqNum uint64; major, minor uint8; shutdown bool; appChan <-chan []byte; controlChan <-chan interface{}; header [13]byte; } func (w *recordWriter) loop(writer io.Writer, appChan <-chan []byte, controlChan <-chan interface{}) { w.writer = writer; w.encryptor = nop{}; w.mac = nop{}; w.appChan = appChan; w.controlChan = controlChan; for !w.shutdown { msg := <-controlChan; if _, ok := msg.(writerEnableApplicationData); ok { break } w.processControlMessage(msg); } for !w.shutdown { // Always process control messages first. if controlMsg, ok := <-controlChan; ok { w.processControlMessage(controlMsg); continue; } select { case controlMsg := <-controlChan: w.processControlMessage(controlMsg) case appMsg := <-appChan: w.processAppMessage(appMsg) } } if !closed(appChan) { go func() { for _ = range appChan { } }() } if !closed(controlChan) { go func() { for _ = range controlChan { } }() } } // fillMACHeader generates a MAC header. See RFC 4346, section 6.2.3.1. func fillMACHeader(header *[13]byte, seqNum uint64, length int, r *record) { header[0] = uint8(seqNum >> 56); header[1] = uint8(seqNum >> 48); header[2] = uint8(seqNum >> 40); header[3] = uint8(seqNum >> 32); header[4] = uint8(seqNum >> 24); header[5] = uint8(seqNum >> 16); header[6] = uint8(seqNum >> 8); header[7] = uint8(seqNum); header[8] = uint8(r.contentType); header[9] = r.major; header[10] = r.minor; header[11] = uint8(length >> 8); header[12] = uint8(length); } func (w *recordWriter) writeRecord(r *record) { w.mac.Reset(); fillMACHeader(&w.header, w.seqNum, len(r.payload), r); w.mac.Write(w.header[0:13]); w.mac.Write(r.payload); macBytes := w.mac.Sum(); w.encryptor.XORKeyStream(r.payload); w.encryptor.XORKeyStream(macBytes); length := len(r.payload) + len(macBytes); w.header[11] = uint8(length >> 8); w.header[12] = uint8(length); w.writer.Write(w.header[8:13]); w.writer.Write(r.payload); w.writer.Write(macBytes); w.seqNum++; } func (w *recordWriter) processControlMessage(controlMsg interface{}) { if controlMsg == nil { w.shutdown = true; return; } switch msg := controlMsg.(type) { case writerChangeCipherSpec: w.writeRecord(&record{recordTypeChangeCipherSpec, w.major, w.minor, []byte{0x01}}); w.encryptor = msg.encryptor; w.mac = msg.mac; w.seqNum = 0; case writerSetVersion: w.major = msg.major; w.minor = msg.minor; case alert: w.writeRecord(&record{recordTypeAlert, w.major, w.minor, []byte{byte(msg.level), byte(msg.error)}}) case handshakeMessage: // TODO(agl): marshal may return a slice too large for a single record. w.writeRecord(&record{recordTypeHandshake, w.major, w.minor, msg.marshal()}) default: fmt.Printf("processControlMessage: unknown %#v\n", msg) } } func (w *recordWriter) processAppMessage(appMsg []byte) { if closed(w.appChan) { w.writeRecord(&record{recordTypeApplicationData, w.major, w.minor, []byte{byte(alertCloseNotify)}}); w.shutdown = true; return; } var done int; for done < len(appMsg) { todo := len(appMsg); if todo > maxTLSPlaintext { todo = maxTLSPlaintext } w.writeRecord(&record{recordTypeApplicationData, w.major, w.minor, appMsg[done : done+todo]}); done += todo; } }