blob: e406de13544a519067c72414d8f7e3e6530cf879 [file] [edit]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the \"License\");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an \"AS IS\" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package rsmsg provides serialization for ResultStore messages.
package rsmsg
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"sync"
"google.golang.org/protobuf/proto"
rspb "google.golang.org/genproto/googleapis/devtools/resultstore/v2"
)
// UploadRequestsToBytes serializes a slice of UploadRequests into a byte slice,
// with each message prefixed by its length.
func UploadRequestsToBytes(reqs []*rspb.UploadRequest) ([]byte, error) {
reqChan := make(chan *rspb.UploadRequest)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for _, req := range reqs {
reqChan <- req
}
close(reqChan)
}()
defer wg.Wait()
var buf bytes.Buffer
if err := WriteUploadRequests(&buf, reqChan); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// BytesToUploadRequests deserializes a byte slice into a slice of UploadRequests.
func BytesToUploadRequests(ctx context.Context, data []byte) ([]*rspb.UploadRequest, error) {
gotReqs := make([]*rspb.UploadRequest, 0)
if len(data) == 0 {
return gotReqs, nil
}
reqChan := make(chan *rspb.UploadRequest)
errChan := make(chan error, 1)
buf := bytes.NewBuffer(data)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
errChan <- ReadUploadRequests(ctx, buf, reqChan)
}()
for req := range reqChan {
gotReqs = append(gotReqs, req)
}
wg.Wait()
if err := <-errChan; err != nil {
return nil, err
}
return gotReqs, nil
}
// WriteUploadRequests writes a stream of UploadRequests from a channel to a writer,
// prefixing each message with its length.
func WriteUploadRequests(output io.Writer, reqs <-chan *rspb.UploadRequest) error {
for req := range reqs {
if err := WriteUploadRequest(output, req); err != nil {
return err
}
}
return nil
}
// WriteUploadRequest writes a single UploadRequest to a writer,
// prefixing the message with its length.
func WriteUploadRequest(output io.Writer, req *rspb.UploadRequest) error {
wireBytes, err := proto.Marshal(req)
if err != nil {
return err
}
if err := binary.Write(output, binary.LittleEndian, uint32(len(wireBytes))); err != nil {
return err
}
if _, err := output.Write(wireBytes); err != nil {
return err
}
return nil
}
// ReadUploadRequest reads a single length-prefixed UploadRequest from a reader.
func ReadUploadRequest(input io.Reader) (*rspb.UploadRequest, error) {
var messageLen uint32
if err := binary.Read(input, binary.LittleEndian, &messageLen); err != nil {
return nil, err
}
messagePayload := make([]byte, messageLen)
if _, err := io.ReadFull(input, messagePayload); err != nil {
return nil, err
}
req := &rspb.UploadRequest{}
if err := proto.Unmarshal(messagePayload, req); err != nil {
return nil, err
}
return req, nil
}
// ReadUploadRequests reads length-prefixed UploadRequests from a reader and sends
// them to a channel. It returns if the reader is exhausted, an error occurs,
// or the context is cancelled.
func ReadUploadRequests(ctx context.Context, input io.Reader, reqs chan<- *rspb.UploadRequest) error {
defer close(reqs)
for {
req, err := ReadUploadRequest(input)
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("Error reading message: %v", err)
}
select {
case <-ctx.Done():
return ctx.Err()
case reqs <- req:
}
}
return nil
}