mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
### Reducing CPU context switching and number of goroutines. Packet capture and packet processing now use only two goroutines which helps to minimize CPU context switches. Spawning too many goroutines is harmful here. ### Optimized packet capture - allocated memory only when required, and only for data which is used Using ZeroCopy methods from libpcap library to avoid unnecessary allocations. Now memory gets allocated ONLY for the valid packets, and only for the packets which have the data. E.g. no SYN/FIN packets are used now. Additionally we now use `sync.Pool` for re-using packet objects, which helps to re-use already allocated memory. ### Simplification and optimization of request/response detection There is no SYN/FIN packets anymore etc. Now only packet payload is used to detect start and end of the packet. More over payload detection now does not require generating a total “message” buffer, and works with individual packet payloads. Message payloads now concatenated from packets only in the end when message is dispatched. Also, before checking if message is complete, added additional check if all received packets in the valid order, e.g. if their SEQ is valid, and no packets are missing. Reworked chunked encoding validation, and now it does not need expensive operation of re-calculating all the chunks. Now it “trust” that client gives valid chunk body, check if packets are in the right order (e.g. SEQ match), and checks if message ends with the right suffix. All is done with 0 allocations. Parsing all Headers using `proto.GetHeaders` was proved to be very slow. Now we only parse the headers we need(and do it only once). Packets gets matched together using ACK, which on high RPS removed chances of duplicating IDs. Additionally, even if packets are received out of order, now it will properly sort them, before dispatching the message. ### Changes in ID generation algorithm Message ID generation and relations between request and response IDs is fully rewritten. Responses now do not have to lookup for request data in order to get the same ID. ID no rely on the fact that SEQ of the first packet of the response should be the same as ACK of the request. If previously Message ID contained random values, like current timestamp, now it has a consistent algorithm which is based on TCP stream id (SrcPort + DstPort + SrcIP/DstIP) and current ACK/SEQ number (to distinguish multiple messages within the same stream). ### BPF filter optimizations When tracking response it now uses a more accurate BPF rule to filter only needed traffic. ### Misc The packet code is now fully moved to tcp/Packet, so packet processing done only once in one place. TCP output now has a 5 second timeout, and has a proper Close method. Fully switching to go modules and removing vendoring.
492 lines
17 KiB
Go
492 lines
17 KiB
Go
package proto
|
|
|
|
import (
|
|
"bytes"
|
|
"net/textproto"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestHeader(t *testing.T) {
|
|
var payload, val []byte
|
|
var headerStart int
|
|
|
|
// Value with space at start
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if val = Header(payload, []byte("Content-Length")); !bytes.Equal(val, []byte("7")) {
|
|
t.Error("Should find header value")
|
|
}
|
|
|
|
// Value with space at end
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 7 \r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if val = Header(payload, []byte("Content-Length")); !bytes.Equal(val, []byte("7")) {
|
|
t.Error("Should find header value without space after 7")
|
|
}
|
|
|
|
// Value without space at start
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length:7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if val = Header(payload, []byte("Content-Length")); !bytes.Equal(val, []byte("7")) {
|
|
t.Error("Should find header value without space after :")
|
|
}
|
|
|
|
// Value is empty
|
|
payload = []byte("GET /p HTTP/1.1\r\nCookie:\r\nHost: www.w3.org\r\n\r\n")
|
|
|
|
if val = Header(payload, []byte("Cookie")); len(val) > 0 {
|
|
t.Error("Should return empty value")
|
|
}
|
|
|
|
// Header not found
|
|
if _, headerStart, _, _, _ = header(payload, []byte("Not-Found")); headerStart != -1 {
|
|
t.Error("Should not found header")
|
|
}
|
|
|
|
// Lower case headers
|
|
payload = []byte("POST /post HTTP/1.1\r\ncontent-length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if val = Header(payload, []byte("Content-Length")); !bytes.Equal(val, []byte("7")) {
|
|
t.Error("Should find lower case 2 word header")
|
|
}
|
|
|
|
payload = []byte("POST /post HTTP/1.1\r\ncontent-length: 7\r\nhost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if val = Header(payload, []byte("host")); !bytes.Equal(val, []byte("www.w3.org")) {
|
|
t.Error("Should find lower case 1 word header")
|
|
}
|
|
}
|
|
|
|
func TestMIMEHeadersEndPos(t *testing.T) {
|
|
head := []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\n")
|
|
payload := []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
end := MIMEHeadersEndPos(payload)
|
|
|
|
if !bytes.Equal(payload[:end], head) {
|
|
t.Error("Wrong headers end position:", end, head, payload[:end])
|
|
}
|
|
}
|
|
|
|
func TestMIMEHeadersStartPos(t *testing.T) {
|
|
headers := []byte("Content-Length: 7\r\nHost: www.w3.org")
|
|
payload := []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
start := MIMEHeadersStartPos(payload)
|
|
end := MIMEHeadersEndPos(payload) - 4
|
|
|
|
if !bytes.Equal(payload[start:end], headers) {
|
|
t.Error("Wrong headers end position:", start, end, payload[start:end])
|
|
}
|
|
}
|
|
|
|
func TestSetHeader(t *testing.T) {
|
|
var payload, payloadAfter []byte
|
|
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post HTTP/1.1\r\nContent-Length: 14\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetHeader(payload, []byte("Content-Length"), []byte("14")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should update header if it exists", string(payload))
|
|
}
|
|
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post HTTP/1.1\r\nUser-Agent: Gor\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetHeader(payload, []byte("User-Agent"), []byte("Gor")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should add header if not found", string(payload))
|
|
}
|
|
invalidPayload := []byte("POST /post HTTP/1.1")
|
|
if invalidPayload = SetHeader(invalidPayload, []byte("User-Agent"), []byte("Gor")); !bytes.Equal(invalidPayload, []byte("POST /post HTTP/1.1")) {
|
|
t.Error("Should not modify payload if request is invalid", string(payload))
|
|
}
|
|
}
|
|
|
|
func TestDeleteHeader(t *testing.T) {
|
|
var payload, payloadAfter []byte
|
|
|
|
payload = []byte("POST /post HTTP/1.1\r\nUser-Agent: Gor\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = DeleteHeader(payload, []byte("User-Agent")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should delete header if found", string(payload), string(payloadAfter))
|
|
}
|
|
|
|
//Whitespace at end of User-Agent
|
|
payload = []byte("POST /post HTTP/1.1\r\nUser-Agent: Gor \r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = DeleteHeader(payload, []byte("User-Agent")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should delete header if found", string(payload), string(payloadAfter))
|
|
}
|
|
}
|
|
|
|
func TestParseHeaders(t *testing.T) {
|
|
payload := [][]byte{[]byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.or"), []byte("g\r\nUser-Ag"), []byte("ent:Chrome\r\n\r\n"), []byte("Fake-Header: asda")}
|
|
|
|
headers := ParseHeaders(bytes.Join(payload, nil))
|
|
|
|
expected := textproto.MIMEHeader{
|
|
"Content-Length": []string{"7"},
|
|
"Host": []string{"www.w3.org"},
|
|
"User-Agent": []string{"Chrome"},
|
|
}
|
|
|
|
if !reflect.DeepEqual(headers, expected) {
|
|
t.Error("Headers do not properly parsed", headers)
|
|
}
|
|
|
|
// Response with Reason phrase
|
|
payload = [][]byte{[]byte("HTTP/1.1 200 OK\r\nContent-Length: 7\r\nHost: www.w3.org\r\nUser-Agent:Chrome\r\n\r\nbody")}
|
|
|
|
headers = ParseHeaders(bytes.Join(payload, nil))
|
|
|
|
if !reflect.DeepEqual(headers, expected) {
|
|
t.Error("Headers do not properly parsed", headers)
|
|
}
|
|
|
|
// Response without Reason phrase
|
|
payload = [][]byte{[]byte("HTTP/1.1 200\r\nContent-Length: 7\r\nHost: www.w3.org\r\nUser-Agent:Chrome\r\n\r\nbody")}
|
|
|
|
headers = ParseHeaders(bytes.Join(payload, nil))
|
|
|
|
if !reflect.DeepEqual(headers, expected) {
|
|
t.Error("Headers do not properly parsed", headers)
|
|
}
|
|
}
|
|
|
|
// See https://github.com/dvyukov/go-fuzz and fuzz.go
|
|
func TestFuzzCrashers(t *testing.T) {
|
|
var crashers = []string{
|
|
"\n:00\n",
|
|
}
|
|
|
|
for _, f := range crashers {
|
|
ParseHeaders([]byte(f))
|
|
}
|
|
}
|
|
|
|
func TestParseHeadersWithComplexUserAgent(t *testing.T) {
|
|
// User-Agent could contain inside ':'
|
|
// Parser should wait for \r\n
|
|
payload := [][]byte{[]byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.or"), []byte("g\r\nUser-Ag"), []byte("ent:Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko\r\n\r\n"), []byte("Fake-Header: asda")}
|
|
|
|
headers := ParseHeaders(bytes.Join(payload, nil))
|
|
|
|
expected := map[string]string{
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
|
|
}
|
|
|
|
if expected["User-Agent"] != headers["User-Agent"][0] {
|
|
t.Errorf("Header 'User-Agent' expected '%s' and parsed: '%s'", expected["User-Agent"], headers["User-Agent"])
|
|
}
|
|
}
|
|
|
|
func TestParseHeadersWithOrigin(t *testing.T) {
|
|
// User-Agent could contain inside ':'
|
|
// Parser should wait for \r\n
|
|
payload := [][]byte{[]byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.or"), []byte("g\r\nReferrer: http://127.0.0.1:3000\r\nOrigi"), []byte("n: https://www.example.com\r\nUser-Ag"), []byte("ent:Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko\r\n\r\n"), []byte("in:https://www.example.com\r\n\r\n"), []byte("Fake-Header: asda")}
|
|
|
|
headers := ParseHeaders(bytes.Join(payload, nil))
|
|
|
|
expected := map[string]string{
|
|
"Origin": "https://www.example.com",
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
|
|
"Referrer": "http://127.0.0.1:3000",
|
|
}
|
|
|
|
if expected["Referrer"] != headers["Referrer"][0] {
|
|
t.Errorf("Header 'Referrer' expected '%s' and parsed: '%s'", expected["Referrer"], headers["Referrer"])
|
|
}
|
|
|
|
if expected["Origin"] != headers["Origin"][0] {
|
|
t.Errorf("Header 'Origin' expected '%s' and parsed: '%s'", expected["Origin"], headers["Origin"])
|
|
}
|
|
|
|
if expected["User-Agent"] != headers["User-Agent"][0] {
|
|
t.Errorf("Header 'User-Agent' expected '%s' and parsed: '%s'", expected["User-Agent"], headers["User-Agent"])
|
|
}
|
|
}
|
|
|
|
func TestPath(t *testing.T) {
|
|
var path, payload []byte
|
|
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if path = Path(payload); !bytes.Equal(path, []byte("/post")) {
|
|
t.Error("Should find path", string(path))
|
|
}
|
|
|
|
payload = []byte("GET /get\r\n\r\nHost: www.w3.org\r\n\r\n")
|
|
|
|
if path = Path(payload); !bytes.Equal(path, nil) {
|
|
t.Error("1Should not find path", string(path))
|
|
}
|
|
|
|
payload = []byte("GET /get\n")
|
|
|
|
if path = Path(payload); !bytes.Equal(path, nil) {
|
|
t.Error("2Should not find path", string(path))
|
|
}
|
|
|
|
payload = []byte("GET /get")
|
|
|
|
if path = Path(payload); !bytes.Equal(path, nil) {
|
|
t.Error("3Should not find path", string(path))
|
|
}
|
|
}
|
|
|
|
func TestStatus(t *testing.T) {
|
|
var status, payload []byte
|
|
|
|
payload = []byte("HTTP/1.1 200 OK\r\n")
|
|
if status = Status(payload); !bytes.Equal(status, []byte("200")) {
|
|
t.Error("Should find status 200 but:", string(status))
|
|
}
|
|
|
|
payload = []byte("HTTP/1.1 200\r\n")
|
|
if status = Status(payload); !bytes.Equal(status, []byte("200")) {
|
|
t.Error("1Should find status 200 but:", string(status))
|
|
}
|
|
|
|
payload = []byte("HTTP/1.1 404 Not Found\r\n")
|
|
if status = Status(payload); !bytes.Equal(status, []byte("404")) {
|
|
t.Error("2Should find status 404 but:", string(status))
|
|
}
|
|
}
|
|
|
|
func TestSetPath(t *testing.T) {
|
|
var payload, payloadAfter []byte
|
|
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /new_path HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetPath(payload, []byte("/new_path")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should replace path", string(payload))
|
|
}
|
|
|
|
}
|
|
|
|
func TestPathParam(t *testing.T) {
|
|
var payload []byte
|
|
|
|
payload = []byte("POST /post?param=test&user_id=1&d_type=1&type=2&d_type=3 HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if val, _, _ := PathParam(payload, []byte("param")); !bytes.Equal(val, []byte("test")) {
|
|
t.Error("Should detect attribute", string(val))
|
|
}
|
|
|
|
if val, _, _ := PathParam(payload, []byte("user_id")); !bytes.Equal(val, []byte("1")) {
|
|
t.Error("Should detect attribute", string(val))
|
|
}
|
|
|
|
if val, _, _ := PathParam(payload, []byte("type")); !bytes.Equal(val, []byte("2")) {
|
|
t.Error("Should detect attribute", string(val))
|
|
}
|
|
|
|
if val, _, _ := PathParam(payload, []byte("d_type")); !bytes.Equal(val, []byte("1")) {
|
|
// this function is not designed for cases with duplicate param keys
|
|
t.Error("Should detect attribute", string(val))
|
|
}
|
|
}
|
|
|
|
func TestSetPathParam(t *testing.T) {
|
|
var payload, payloadAfter []byte
|
|
|
|
payload = []byte("POST /post?param=test&user_id=1 HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post?param=new&user_id=1 HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetPathParam(payload, []byte("param"), []byte("new")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should replace existing value", string(payload))
|
|
}
|
|
|
|
payload = []byte("POST /post?param=test&user_id=1 HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post?param=test&user_id=2 HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetPathParam(payload, []byte("user_id"), []byte("2")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should replace existing value", string(payload))
|
|
}
|
|
|
|
payload = []byte("POST /post HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post?param=test HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetPathParam(payload, []byte("param"), []byte("test")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should set param if url have no params", string(payload))
|
|
}
|
|
|
|
payload = []byte("POST /post?param=test HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post?param=test&user_id=1 HTTP/1.1\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetPathParam(payload, []byte("user_id"), []byte("1")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should set param at the end if url params", string(payload))
|
|
}
|
|
}
|
|
|
|
func TestSetHostHTTP10(t *testing.T) {
|
|
var payload, payloadAfter []byte
|
|
|
|
payload = []byte("POST http://example.com/post HTTP/1.0\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST http://new.com/post HTTP/1.0\r\nContent-Length: 7\r\nHost: www.w3.org\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetHost(payload, []byte("http://new.com"), []byte("new.com")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should replace host", string(payload))
|
|
}
|
|
|
|
payload = []byte("POST /post HTTP/1.0\r\nContent-Length: 7\r\nHost: example.com\r\n\r\na=1&b=2")
|
|
payloadAfter = []byte("POST /post HTTP/1.0\r\nContent-Length: 7\r\nHost: new.com\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetHost(payload, nil, []byte("new.com")); !bytes.Equal(payload, payloadAfter) {
|
|
t.Error("Should replace host", string(payload))
|
|
}
|
|
|
|
payload = []byte("POST /post HTTP/1.0\r\nContent-Length: 7\r\n\r\na=1&b=2")
|
|
|
|
if payload = SetHost(payload, nil, []byte("new.com")); !bytes.Equal(payload, payload) {
|
|
t.Error("Should replace host", string(payload))
|
|
}
|
|
}
|
|
|
|
func TestHasResponseTitle(t *testing.T) {
|
|
var m = map[string]bool{
|
|
"HTTP": false,
|
|
"": false,
|
|
"HTTP/1.1 100 Continue": false,
|
|
"HTTP/1.1 100 Continue\r\n": true,
|
|
"HTTP/1.1 \r\n": false,
|
|
"HTTP/4.0 100Continue\r\n": false,
|
|
"HTTP/1.0 100Continue\r\n": false,
|
|
"HTTP/1.0 10r Continue\r\n": false,
|
|
"HTTP/1.1 200\r\n": true,
|
|
"HTTP/1.1 200\r\nServer: Tengine\r\nContent-Length: 0\r\nConnection: close\r\n\r\n": true,
|
|
}
|
|
for k, v := range m {
|
|
if HasResponseTitle([]byte(k)) != v {
|
|
t.Errorf("%q should yield %v", k, v)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHasRequestTitle(t *testing.T) {
|
|
var m = map[string]bool{
|
|
"POST /post HTTP/1.0\r\n": true,
|
|
"": false,
|
|
"POST /post HTTP/1.\r\n": false,
|
|
"POS /post HTTP/1.1\r\n": false,
|
|
"GET / HTTP/1.1\r\n": true,
|
|
"GET / HTTP/1.1\r": false,
|
|
"GET / HTTP/1.400\r\n": false,
|
|
}
|
|
for k, v := range m {
|
|
if HasRequestTitle([]byte(k)) != v {
|
|
t.Errorf("%q should yield %v", k, v)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheckChunks(t *testing.T) {
|
|
var m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n"
|
|
chunkEnd, _ := CheckChunked([]byte(m))
|
|
expected := len(m)
|
|
if chunkEnd != expected {
|
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
|
}
|
|
|
|
m = "7\r\nMozia\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n"
|
|
chunkEnd, _ = CheckChunked([]byte(m))
|
|
if chunkEnd != 0 {
|
|
t.Errorf("expected %d to equal %d", chunkEnd, 0)
|
|
}
|
|
|
|
// with trailers
|
|
m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\nEXpires"
|
|
chunkEnd, _ = CheckChunked([]byte(m))
|
|
expected = len(m) - 7
|
|
if chunkEnd != expected {
|
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
|
}
|
|
|
|
// last chunk inside the body
|
|
// with trailers
|
|
m = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n3\r\n0\r\n\r\n0\r\n\r\nEXpires"
|
|
chunkEnd, _ = CheckChunked([]byte(m))
|
|
expected = len(m) - 7
|
|
if chunkEnd != expected {
|
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
|
}
|
|
|
|
// checks with chucks-extensions
|
|
m = "4\r\nWiki\r\n5\r\npedia\r\nE; name='quoted string'\r\n in\r\n\r\nchunks.\r\n3\r\n0\r\n\r\n0\r\n\r\nEXpires"
|
|
chunkEnd, _ = CheckChunked([]byte(m))
|
|
expected = len(m) - 7
|
|
if chunkEnd != expected {
|
|
t.Errorf("expected %d to equal %d", chunkEnd, expected)
|
|
}
|
|
}
|
|
|
|
func TestHasFullPayload(t *testing.T) {
|
|
var m string
|
|
var got, expected bool
|
|
|
|
got = HasFullPayload(nil,
|
|
[]byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n"),
|
|
[]byte("Transfer-Encoding: chunked\r\n\r\n"),
|
|
[]byte("7\r\nMozilla\r\n9\r\nDeveloper\r\n"),
|
|
[]byte("7\r\nNetwork\r\n0\r\n\r\n"))
|
|
expected = true
|
|
if got != expected {
|
|
t.Errorf("expected %v to equal %v", got, expected)
|
|
}
|
|
|
|
// check chunks with trailers
|
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nTrailer: Expires\r\n\r\n7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\nExpires: Wed, 21 Oct 2015 07:28:00 GMT\r\n\r\n"
|
|
got = HasFullPayload(nil, []byte(m))
|
|
expected = true
|
|
if got != expected {
|
|
t.Errorf("expected %v to equal %v", got, expected)
|
|
}
|
|
|
|
// check with missing trailers
|
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\nTrailer: Expires\r\n\r\n7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\nExpires: Wed, 21 Oct 2015 07:28:00"
|
|
got = HasFullPayload(nil, []byte(m))
|
|
expected = false
|
|
if got != expected {
|
|
t.Errorf("expected %v to equal %v", got, expected)
|
|
}
|
|
|
|
// check with content-length
|
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\nMozillaDeveloperNetwork"
|
|
got = HasFullPayload(nil, []byte(m))
|
|
expected = true
|
|
if got != expected {
|
|
t.Errorf("expected %v to equal %v", got, expected)
|
|
}
|
|
|
|
// check missing total length
|
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 23\r\n\r\nMozillaDeveloperNet"
|
|
got = HasFullPayload(nil, []byte(m))
|
|
expected = false
|
|
if got != expected {
|
|
t.Errorf("expected %v to equal %v", got, expected)
|
|
}
|
|
|
|
// check with no body
|
|
m = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"
|
|
got = HasFullPayload(nil, []byte(m))
|
|
expected = true
|
|
if got != expected {
|
|
t.Errorf("expected %v to equal %v", got, expected)
|
|
}
|
|
}
|
|
|
|
func BenchmarkHasFullPayload(b *testing.B) {
|
|
data := []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\n111111111111111111111111111111\r\n0\r\n\r\n")
|
|
for i := 0; i < b.N; i++ {
|
|
if !HasFullPayload(nil, data) {
|
|
b.Fail()
|
|
}
|
|
}
|
|
}
|