libp2p-test-plans/perf/impl/https/v0.1/main.go

258 lines
6.0 KiB
Go

package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/binary"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"io"
"log"
"math/big"
"net"
"net/http"
"strconv"
"time"
)
const blockSize = 64 << 10
func handleRequest(w http.ResponseWriter, r *http.Request) {
u64Buf := make([]byte, 8)
if _, err := io.ReadFull(r.Body, u64Buf); err != nil {
log.Printf("reading upload size failed: %s", err)
w.WriteHeader(http.StatusBadRequest)
return
}
bytesToSend := binary.BigEndian.Uint64(u64Buf)
if _, err := drainStream(r.Body); err != nil {
log.Printf("draining stream failed: %s", err)
w.WriteHeader(http.StatusBadRequest)
return
}
r.Header.Set("Content-Type", "application/octet-stream")
r.Header.Set("Content-Length", strconv.FormatUint(bytesToSend, 10))
if err := sendBytes(w, bytesToSend); err != nil {
log.Printf("sending response failed: %s", err)
return
}
}
type nullReader struct {
N uint64
read uint64
}
var _ io.Reader = &nullReader{}
func (r *nullReader) Read(b []byte) (int, error) {
remaining := r.N - r.read
l := uint64(len(b))
if uint64(len(b)) > remaining {
l = remaining
}
r.read += l
if r.read == r.N {
return int(l), io.EOF
}
return int(l), nil
}
func runClient(serverAddr string, uploadBytes, downloadBytes uint64) (time.Duration, error) {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, downloadBytes)
req, err := http.NewRequest(
http.MethodPost,
fmt.Sprintf("https://%s/", serverAddr),
io.MultiReader(
bytes.NewReader(b),
&nullReader{N: uploadBytes},
),
)
if err != nil {
return 0, err
}
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Length", strconv.FormatUint(uploadBytes+8, 10))
startTime := time.Now()
resp, err := client.Do(req)
if err != nil {
return 0, err
}
if resp.StatusCode != http.StatusOK {
return 0, fmt.Errorf("server returned non-OK status: %d %s", resp.StatusCode, resp.Status)
}
defer resp.Body.Close()
n, err := drainStream(resp.Body)
if err != nil {
return 0, fmt.Errorf("error reading response: %w", err)
}
if n != downloadBytes {
return 0, fmt.Errorf("expected %d bytes in response, but received %d", downloadBytes, n)
}
return time.Since(startTime), nil
}
func generateEphemeralCertificate() (tls.Certificate, error) {
// Generate an ECDSA private key
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
// Set up the certificate template
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Ephemeral Cert"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
}
// Set the IP address if required
ip := net.ParseIP("127.0.0.1")
if ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
}
// Create a self-signed certificate
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
if err != nil {
return tls.Certificate{}, err
}
// PEM encode the certificate and private key
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
privKeyBytes, err := x509.MarshalECPrivateKey(privKey)
if err != nil {
return tls.Certificate{}, err
}
privKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privKeyBytes})
// Create a tls.Certificate from the PEM encoded certificate and private key
cert, err := tls.X509KeyPair(certPEM, privKeyPEM)
if err != nil {
return tls.Certificate{}, err
}
return cert, nil
}
type Result struct {
Latency float64 `json:"latency"`
}
func main() {
runServer := flag.Bool("run-server", false, "Should run as server")
serverAddr := flag.String("server-address", "", "Server address")
_ = flag.String("transport", "", "Transport to use")
uploadBytes := flag.Uint64("upload-bytes", 0, "Upload bytes")
downloadBytes := flag.Uint64("download-bytes", 0, "Download bytes")
flag.Parse()
if *runServer {
// Generate an ephemeral TLS certificate and private key
cert, err := generateEphemeralCertificate()
if err != nil {
log.Fatalf("Error generating ephemeral certificate: %v\n", err)
}
// Parse the server address
_, port, err := net.SplitHostPort(*serverAddr)
if err != nil {
log.Fatalf("Invalid server address: %v\n", err)
}
// Create a new HTTPS server with the ephemeral certificate
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
server := &http.Server{
Addr: ":" + port,
TLSConfig: tlsConfig,
}
http.HandleFunc("/", handleRequest)
// Start the HTTPS server
fmt.Printf("Starting HTTPS server on port %s\n", port)
err = server.ListenAndServeTLS("", "")
if err != nil {
fmt.Printf("Error starting HTTPS server: %v\n", err)
}
} else {
// Client mode
if *serverAddr == "" {
flag.Usage()
log.Fatal("Error: Please provide valid server-address flags for client mode.")
}
// Run the client and print the results
latency, err := runClient(*serverAddr, *uploadBytes, *downloadBytes)
if err != nil {
log.Fatal(err)
}
jsonB, err := json.Marshal(Result{
Latency: latency.Seconds(),
})
if err != nil {
log.Fatalf("failed to marshal perf result: %s", err)
}
fmt.Println(string(jsonB))
}
}
func sendBytes(s io.Writer, bytesToSend uint64) error {
buf := make([]byte, blockSize)
for bytesToSend > 0 {
toSend := buf
if bytesToSend < blockSize {
toSend = buf[:bytesToSend]
}
n, err := s.Write(toSend)
if err != nil {
return err
}
bytesToSend -= uint64(n)
}
return nil
}
func drainStream(s io.Reader) (uint64, error) {
var recvd int64
recvd, err := io.Copy(io.Discard, s)
if err != nil && err != io.EOF {
return uint64(recvd), err
}
return uint64(recvd), nil
}