github.com/ezoic/ws@v1.0.4-0.20220713205711-5c1d69e074c5/cipher.go (about)

     1  package ws
     2  
     3  import (
     4  	"encoding/binary"
     5  	"unsafe"
     6  )
     7  
     8  // Cipher applies XOR cipher to the payload using mask.
     9  // Offset is used to cipher chunked data (e.g. in io.Reader implementations).
    10  //
    11  // To convert masked data into unmasked data, or vice versa, the following
    12  // algorithm is applied.  The same algorithm applies regardless of the
    13  // direction of the translation, e.g., the same steps are applied to
    14  // mask the data as to unmask the data.
    15  func Cipher(payload []byte, mask [4]byte, offset int) {
    16  	n := len(payload)
    17  	if n < 8 {
    18  		for i := 0; i < n; i++ {
    19  			payload[i] ^= mask[(offset+i)%4]
    20  		}
    21  		return
    22  	}
    23  
    24  	// Calculate position in mask due to previously processed bytes number.
    25  	mpos := offset % 4
    26  	// Count number of bytes will processed one by one from the beginning of payload.
    27  	ln := remain[mpos]
    28  	// Count number of bytes will processed one by one from the end of payload.
    29  	// This is done to process payload by 8 bytes in each iteration of main loop.
    30  	rn := (n - ln) % 8
    31  
    32  	for i := 0; i < ln; i++ {
    33  		payload[i] ^= mask[(mpos+i)%4]
    34  	}
    35  	for i := n - rn; i < n; i++ {
    36  		payload[i] ^= mask[(mpos+i)%4]
    37  	}
    38  
    39  	// We should cast mask to uint32 with unsafe instead of encoding.BigEndian
    40  	// to avoid care of os dependent byte order. That is, on any endianess mask
    41  	// and payload will be presented with the same order. In other words, we
    42  	// could not use encoding.BigEndian on xoring payload as uint64.
    43  	m := *(*uint32)(unsafe.Pointer(&mask))
    44  	m2 := uint64(m)<<32 | uint64(m)
    45  
    46  	// Skip already processed right part.
    47  	// Get number of uint64 parts remaining to process.
    48  	n = (n - ln - rn) >> 3
    49  	for i := 0; i < n; i++ {
    50  		idx := ln + (i << 3)
    51  		p := binary.LittleEndian.Uint64(payload[idx : idx+8])
    52  		p = p ^ m2
    53  		binary.LittleEndian.PutUint64(payload[idx:idx+8], p)
    54  	}
    55  }
    56  
    57  // remain maps position in masking key [0,4) to number
    58  // of bytes that need to be processed manually inside Cipher().
    59  var remain = [4]int{0, 3, 2, 1}