package faketls import ( "bytes" "io" "testing" "github.com/stretchr/testify/require" ) // TestReadSkipsChangeCipherSpec ensures interleaved ChangeCipherSpec records // do not pollute the Application-data stream. Earlier behaviour wrote the // 1-byte CCS payload into readBuf, which desynced the obfuscated2 CTR // keystream and produced "msg_key is invalid" on decrypted MTProto messages. func TestReadSkipsChangeCipherSpec(t *testing.T) { a := require.New(t) wire := bytes.NewBuffer(nil) // CCS record (1 byte = 0x01) _, err := writeRecord(wire, record{ Type: RecordTypeChangeCipherSpec, Version: Version12Bytes, Data: []byte{0x01}, }) a.NoError(err) // Application record carrying our payload payload := []byte("hello-mtproto-bytes") _, err = writeRecord(wire, record{ Type: RecordTypeApplication, Version: Version12Bytes, Data: payload, }) a.NoError(err) // Another CCS in the middle _, err = writeRecord(wire, record{ Type: RecordTypeChangeCipherSpec, Version: Version12Bytes, Data: []byte{0x01}, }) a.NoError(err) // Second application record more := []byte("second-payload") _, err = writeRecord(wire, record{ Type: RecordTypeApplication, Version: Version12Bytes, Data: more, }) a.NoError(err) tls := NewFakeTLS(zeroReader{}, &readonly{r: wire}) got, err := io.ReadAll(io.LimitReader(tls, int64(len(payload)+len(more)))) a.NoError(err) a.Equal(append(append([]byte(nil), payload...), more...), got) } // readonly adapts an io.Reader to io.ReadWriter (NewFakeTLS demands one). type readonly struct{ r io.Reader } func (r *readonly) Read(p []byte) (int, error) { return r.r.Read(p) } func (r *readonly) Write(p []byte) (int, error) { return len(p), nil }