...

Source file src/golang.conradwood.net/go-easyops/utils/packet_writer.go

Documentation: golang.conradwood.net/go-easyops/utils

     1  package utils
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  )
     9  
    10  /*
    11  This is part of the Packetizer toolset.
    12  
    13  A PacketWriter writes arbitrary data to an io.Writer. Each call to [PacketWriter.Write] is assumed to be one packet.
    14  The PacketWriter sends extra data to the io.Writer to allow a [PacketReader] to identify and reassemble each packet.
    15  
    16  The algorithm to send any one packet can be summarised like so:
    17  
    18   1. Send one extra byte, the start- byte
    19   2. Send the payload upto, but not including any start, stop or escape bytes in the payload
    20   3. for any start, stop or escape byte, insert an extra escape byte
    21   4. repeat send until all bytes are send
    22   5. send one extra byte, the escape-byte
    23  
    24  This algorithm is sufficient to send any data, 8-bit clean, across any io.Writer and reassemble it on the receiving side ot he
    25  stream.
    26  The canonical implementation to reassemble the packets is [NewPacketReader].
    27  */
    28  func NewPacketWriter(r io.Writer, start, escape, stop byte) (*PacketWriter, error) {
    29  	err := check_valid(start, escape, stop)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	res := &PacketWriter{
    34  		start:  start,
    35  		stop:   stop,
    36  		esc:    escape,
    37  		writer: r,
    38  	}
    39  	return res, nil
    40  }
    41  
    42  type PacketWriter struct {
    43  	start  byte
    44  	stop   byte
    45  	esc    byte
    46  	writer io.Writer
    47  	closed bool
    48  	wrlock sync.Mutex
    49  }
    50  
    51  func (pr *PacketWriter) Close() error {
    52  	pr.closed = true
    53  	iw, castable := pr.writer.(io.WriteCloser)
    54  	if castable {
    55  		err := iw.Close()
    56  		return err
    57  	}
    58  	return nil
    59  }
    60  
    61  // write a packet (using buf as the payload)
    62  func (pr *PacketWriter) Write(buf []byte) (int, error) {
    63  	if pr.closed {
    64  		return 0, fmt.Errorf("write to closed packet writer")
    65  	}
    66  	if len(buf) > 8192 {
    67  		return 0, fmt.Errorf("exceeded max packet size")
    68  	}
    69  
    70  	// TODO: convert to streaming for more efficiency
    71  	pkt := bytes.Buffer{}
    72  	pkt.Write([]byte{pr.start})
    73  	for _, b := range buf {
    74  		if b == pr.start || b == pr.stop || b == pr.esc {
    75  			pkt.Write([]byte{pr.esc})
    76  		}
    77  		pkt.Write([]byte{b})
    78  	}
    79  	pkt.Write([]byte{pr.stop})
    80  	pktbytes := pkt.Bytes()
    81  
    82  	pr.wrlock.Lock() // start lock for atomic write for any one packet
    83  	n, err := pr.writer.Write(pktbytes)
    84  	pr.wrlock.Unlock() // end lock for atomic write for any one packet
    85  
    86  	if err != nil {
    87  		return n, err
    88  	}
    89  	return len(buf), nil
    90  }
    91  

View as plain text