go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/butler/bundler/datagramParser.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bundler
    16  
    17  import (
    18  	"io"
    19  
    20  	"go.chromium.org/luci/common/data/recordio"
    21  	"go.chromium.org/luci/logdog/api/logpb"
    22  )
    23  
    24  // datagramParser is a parser implementation for the LogDog datagram stream
    25  // type.
    26  type datagramParser struct {
    27  	baseParser
    28  
    29  	// maxSize is the maximum allowed datagram size. Datagrams larger than this
    30  	// will result in a processing error.
    31  	maxSize int64
    32  
    33  	// seq is the current datagram sequence number.
    34  	seq int64
    35  
    36  	// remaining is the amount of data remaining in a datagram that has previously
    37  	// been emitted partially.
    38  	//
    39  	// This will be zero if we're not continuing a partial datagram.
    40  	remaining int64
    41  	// index is the index of this datagram. This is zero unless the datagram is
    42  	// a continuation of a previous partial datagram, in which case this is the
    43  	// continuation's index.
    44  	index int64
    45  	// size is the size of the current partial datagram.
    46  	//
    47  	// This value is only valid if we're continuing a partial datagram (i.e., if
    48  	// remaining is non-zero).
    49  	size int64
    50  }
    51  
    52  var _ parser = (*datagramParser)(nil)
    53  
    54  func (s *datagramParser) nextEntry(c *constraints) (*logpb.LogEntry, error) {
    55  	// Use the current Buffer timestamp.
    56  	ts, has := s.firstChunkTime()
    57  	if !has {
    58  		// No chunks, so no data.
    59  		return nil, nil
    60  	}
    61  
    62  	// If remaining is zero, we don't have a buffered size header.
    63  	//
    64  	// Note that zero-size datagrams will store zero here on load; however, such
    65  	// datagrams will never fail to emit a LogEntry, so s.remaining will have been
    66  	// reset to zero by the next call.
    67  	if s.remaining == 0 {
    68  		bv := s.View()
    69  
    70  		// Read the next datagram size header.
    71  		rio := recordio.NewReader(bv, s.maxSize)
    72  		size, _, err := rio.ReadFrame()
    73  		if err != nil {
    74  			switch err {
    75  			case io.EOF, io.ErrUnexpectedEOF:
    76  				// Not enough data for a size header.
    77  				return nil, nil
    78  
    79  			case recordio.ErrFrameTooLarge:
    80  				return nil, recordio.ErrFrameTooLarge
    81  			}
    82  			// Other errors should not be possible, since all operations are against
    83  			// in-memory buffers.
    84  			memoryCorruption(err)
    85  		}
    86  
    87  		s.index = 0
    88  		s.size = size
    89  		s.remaining = size
    90  
    91  		// Don't need to read the size header again.
    92  		s.Consume(bv.Consumed())
    93  	}
    94  
    95  	// If we read this, will it be partial?
    96  	emitCount := s.remaining
    97  	continued := false
    98  	if emitCount > int64(c.limit) {
    99  		continued = true
   100  		emitCount = int64(c.limit)
   101  	}
   102  
   103  	bv := s.ViewLimit(s.remaining)
   104  	if r := bv.Remaining(); r < emitCount {
   105  		// Not enough buffered data to complete the datagram in one round.
   106  		continued = true
   107  		emitCount = r
   108  	}
   109  	if s.remaining > 0 && emitCount == 0 {
   110  		// The datagram has data, but we can't emit any of it. No point in issuing
   111  		// a zero-size partial datagram.
   112  		return nil, nil
   113  	}
   114  
   115  	// We're not willing to emit a partial datagram unless we're allowed to
   116  	// split.
   117  	if continued && !c.allowSplit {
   118  		return nil, nil
   119  	}
   120  
   121  	dg := logpb.Datagram{}
   122  	if continued || s.index > 0 {
   123  		dg.Partial = &logpb.Datagram_Partial{
   124  			Index: uint32(s.index),
   125  			Size:  uint64(s.size),
   126  			Last:  !continued,
   127  		}
   128  	}
   129  	if emitCount > 0 {
   130  		dg.Data = make([]byte, emitCount)
   131  		bv.Read(dg.Data)
   132  		s.Consume(emitCount)
   133  	}
   134  
   135  	le := s.baseLogEntry(ts)
   136  	le.Sequence = uint64(s.seq)
   137  	le.Content = &logpb.LogEntry_Datagram{Datagram: &dg}
   138  
   139  	if !continued {
   140  		s.seq++
   141  		s.remaining = 0
   142  		// Will reset remaining partial fields on next read, since remaining == 0.
   143  	} else {
   144  		s.index++
   145  		s.remaining -= emitCount
   146  	}
   147  	return le, nil
   148  }