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}