github.com/simonmittag/ws@v1.1.0-rc.5.0.20210419231947-82b846128245/cipher.go (about) 1 package ws 2 3 import ( 4 "encoding/binary" 5 ) 6 7 // Cipher applies XOR cipher to the payload using mask. 8 // Offset is used to cipher chunked data (e.g. in io.Reader implementations). 9 // 10 // To convert masked data into unmasked data, or vice versa, the following 11 // algorithm is applied. The same algorithm applies regardless of the 12 // direction of the translation, e.g., the same steps are applied to 13 // mask the data as to unmask the data. 14 func Cipher(payload []byte, mask [4]byte, offset int) { 15 n := len(payload) 16 if n < 8 { 17 for i := 0; i < n; i++ { 18 payload[i] ^= mask[(offset+i)%4] 19 } 20 return 21 } 22 23 // Calculate position in mask due to previously processed bytes number. 24 mpos := offset % 4 25 // Count number of bytes will processed one by one from the beginning of payload. 26 ln := remain[mpos] 27 // Count number of bytes will processed one by one from the end of payload. 28 // This is done to process payload by 8 bytes in each iteration of main loop. 29 rn := (n - ln) % 8 30 31 for i := 0; i < ln; i++ { 32 payload[i] ^= mask[(mpos+i)%4] 33 } 34 for i := n - rn; i < n; i++ { 35 payload[i] ^= mask[(mpos+i)%4] 36 } 37 38 // NOTE: we use here binary.LittleEndian regardless of what is real 39 // endianness on machine is. To do so, we have to use binary.LittleEndian in 40 // the masking loop below as well. 41 var ( 42 m = binary.LittleEndian.Uint32(mask[:]) 43 m2 = uint64(m)<<32 | uint64(m) 44 ) 45 // Skip already processed right part. 46 // Get number of uint64 parts remaining to process. 47 n = (n - ln - rn) >> 3 48 for i := 0; i < n; i++ { 49 var ( 50 j = ln + (i << 3) 51 chunk = payload[j : j+8] 52 ) 53 p := binary.LittleEndian.Uint64(chunk) 54 p = p ^ m2 55 binary.LittleEndian.PutUint64(chunk, p) 56 } 57 } 58 59 // remain maps position in masking key [0,4) to number 60 // of bytes that need to be processed manually inside Cipher(). 61 var remain = [4]int{0, 3, 2, 1}