package des

import "errors"

// author
// https://github.com/noVNC/noVNC
// core/crypto/des.js

/*
 * Ported from Flashlight VNC ActionScript implementation:
 *     http://www.wizhelp.com/flashlight-vnc/
 *
 * Full attribution follows:
 *
 * -------------------------------------------------------------------------
 *
 * This DES class has been extracted from package Acme.Crypto for use in VNC.
 * The unnecessary odd parity code has been removed.
 *
 * These changes are:
 *  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *

 * DesCipher - the DES encryption method
 *
 * The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
 *
 * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
 * without fee is hereby granted, provided that this copyright notice is kept
 * intact.
 *
 * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
 * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
 * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 *
 * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
 * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
 * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
 * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
 * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
 * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
 * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  WIDGET WORKSHOP
 * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
 * HIGH RISK ACTIVITIES.
 *
 *
 * The rest is:
 *
 * Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Visit the ACME Labs Java page for up-to-date versions of this and other
 * fine Java utilities: http://www.acme.com/java/
 */

var PC2 = []uint32{
	13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3,
	25, 7, 15, 6, 26, 19, 12, 1, 40, 51, 30, 36, 46, 54, 29, 39,
	50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31,
}

var totrot = []uint32{
	1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28,
}

var (
	SP1 []uint32
	SP2 []uint32
	SP3 []uint32
	SP4 []uint32
	SP5 []uint32
	SP6 []uint32
	SP7 []uint32
	SP8 []uint32
)

func init() {
	var z = uint32(0)
	var a = uint32(1) << 16
	var b = uint32(1) << 24
	var c = a | b
	var d = uint32(1) << 2
	var e = uint32(1) << 10
	var f = d | e

	SP1 = []uint32{
		c | e, z | z, a | z, c | f, c | d, a | f, z | d, a | z, z | e, c | e, c | f, z | e, b | f, c | d, b | z, z | d,
		z | f, b | e, b | e, a | e, a | e, c | z, c | z, b | f, a | d, b | d, b | d, a | d, z | z, z | f, a | f, b | z,
		a | z, c | f, z | d, c | z, c | e, b | z, b | z, z | e, c | d, a | z, a | e, b | d, z | e, z | d, b | f, a | f,
		c | f, a | d, c | z, b | f, b | d, z | f, a | f, c | e, z | f, b | e, b | e, z | z, a | d, a | e, z | z, c | d,
	}

	a = 1 << 20
	b = 1 << 31
	c = a | b
	d = 1 << 5
	e = 1 << 15
	f = d | e
	SP2 = []uint32{
		c | f, b | e, z | e, a | f, a | z, z | d, c | d, b | f, b | d, c | f, c | e, b | z, b | e, a | z, z | d, c | d,
		a | e, a | d, b | f, z | z, b | z, z | e, a | f, c | z, a | d, b | d, z | z, a | e, z | f, c | e, c | z, z | f,
		z | z, a | f, c | d, a | z, b | f, c | z, c | e, z | e, c | z, b | e, z | d, c | f, a | f, z | d, z | e, b | z,
		z | f, c | e, a | z, b | d, a | d, b | f, b | d, a | d, a | e, z | z, b | e, z | f, b | z, c | d, c | f, a | e,
	}

	a = 1 << 17
	b = 1 << 27
	c = a | b
	d = 1 << 3
	e = 1 << 9
	f = d | e
	SP3 = []uint32{
		z | f, c | e, z | z, c | d, b | e, z | z, a | f, b | e, a | d, b | d, b | d, a | z, c | f, a | d, c | z, z | f,
		b | z, z | d, c | e, z | e, a | e, c | z, c | d, a | f, b | f, a | e, a | z, b | f, z | d, c | f, z | e, b | z,
		c | e, b | z, a | d, z | f, a | z, c | e, b | e, z | z, z | e, a | d, c | f, b | e, b | d, z | e, z | z, c | d,
		b | f, a | z, b | z, c | f, z | d, a | f, a | e, b | d, c | z, b | f, z | f, c | z, a | f, z | d, c | d, a | e,
	}

	a = 1 << 13
	b = 1 << 23
	c = a | b
	d = 1 << 0
	e = 1 << 7
	f = d | e
	SP4 = []uint32{
		c | d, a | f, a | f, z | e, c | e, b | f, b | d, a | d, z | z, c | z, c | z, c | f, z | f, z | z, b | e, b | d,
		z | d, a | z, b | z, c | d, z | e, b | z, a | d, a | e, b | f, z | d, a | e, b | e, a | z, c | e, c | f, z | f,
		b | e, b | d, c | z, c | f, z | f, z | z, z | z, c | z, a | e, b | e, b | f, z | d, c | d, a | f, a | f, z | e,
		c | f, z | f, z | d, a | z, b | d, a | d, c | e, b | f, a | d, a | e, b | z, c | d, z | e, b | z, a | z, c | e,
	}

	a = 1 << 25
	b = 1 << 30
	c = a | b
	d = 1 << 8
	e = 1 << 19
	f = d | e
	SP5 = []uint32{
		z | d, a | f, a | e, c | d, z | e, z | d, b | z, a | e, b | f, z | e, a | d, b | f, c | d, c | e, z | f, b | z,
		a | z, b | e, b | e, z | z, b | d, c | f, c | f, a | d, c | e, b | d, z | z, c | z, a | f, a | z, c | z, z | f,
		z | e, c | d, z | d, a | z, b | z, a | e, c | d, b | f, a | d, b | z, c | e, a | f, b | f, z | d, a | z, c | e,
		c | f, z | f, c | z, c | f, a | e, z | z, b | e, c | z, z | f, a | d, b | d, z | e, z | z, b | e, a | f, b | d,
	}

	a = 1 << 22
	b = 1 << 29
	c = a | b
	d = 1 << 4
	e = 1 << 14
	f = d | e
	SP6 = []uint32{
		b | d, c | z, z | e, c | f, c | z, z | d, c | f, a | z, b | e, a | f, a | z, b | d, a | d, b | e, b | z, z | f,
		z | z, a | d, b | f, z | e, a | e, b | f, z | d, c | d, c | d, z | z, a | f, c | e, z | f, a | e, c | e, b | z,
		b | e, z | d, c | d, a | e, c | f, a | z, z | f, b | d, a | z, b | e, b | z, z | f, b | d, c | f, a | e, c | z,
		a | f, c | e, z | z, c | d, z | d, z | e, c | z, a | f, z | e, a | d, b | f, z | z, c | e, b | z, a | d, b | f,
	}

	a = 1 << 21
	b = 1 << 26
	c = a | b
	d = 1 << 1
	e = 1 << 11
	f = d | e
	SP7 = []uint32{
		a | z, c | d, b | f, z | z, z | e, b | f, a | f, c | e, c | f, a | z, z | z, b | d, z | d, b | z, c | d, z | f,
		b | e, a | f, a | d, b | e, b | d, c | z, c | e, a | d, c | z, z | e, z | f, c | f, a | e, z | d, b | z, a | e,
		b | z, a | e, a | z, b | f, b | f, c | d, c | d, z | d, a | d, b | z, b | e, a | z, c | e, z | f, a | f, c | e,
		z | f, b | d, c | f, c | z, a | e, z | z, z | d, c | f, z | z, a | f, c | z, z | e, b | d, b | e, z | e, a | d,
	}

	a = 1 << 18
	b = 1 << 28
	c = a | b
	d = 1 << 6
	e = 1 << 12
	f = d | e
	SP8 = []uint32{
		b | f, z | e, a | z, c | f, b | z, b | f, z | d, b | z, a | d, c | z, c | f, a | e, c | e, a | f, z | e, z | d,
		c | z, b | d, b | e, z | f, a | e, a | d, c | d, c | e, z | f, z | z, z | z, c | d, b | d, b | e, a | f, a | z,
		a | f, a | z, c | e, z | e, z | d, c | d, z | e, a | f, b | e, z | d, b | d, c | z, c | d, b | z, a | z, b | f,
		z | z, c | f, a | d, b | d, c | z, b | e, b | f, z | z, c | f, a | e, a | e, z | f, z | f, a | d, b | z, c | e,
	}
}

type LossBytes []byte

func (b LossBytes) Get(index int) byte {
	if index >= len(b) {
		return 0
	}
	return b[index]
}

var ErrInvalidLength = errors.New("invalid length")

const BlockSize = 8

type DES struct {
	keys []uint32
}

func (d *DES) Encrypt(dst, src []byte) error {
	if len(src)%8 != 0 {
		return ErrInvalidLength
	}
	for i := 0; i < len(src); i += 8 {
		err := d.encode(dst[i:i+8], src[i:i+8])
		if err != nil {
			return err
		}
	}
	return nil
}

func (d *DES) encode(dst, src []byte) error {
	if len(dst) != 8 {
		return ErrInvalidLength
	}
	if len(src) != 8 {
		return ErrInvalidLength
	}

	i := uint32(0)

	l := uint32(0) // left
	r := uint32(0) // right
	x := uint32(0) // accumulator

	l = uint32(src[i]) << 24
	i += 1
	l |= uint32(src[i]) << 16
	i += 1
	l |= uint32(src[i]) << 8
	i += 1
	l |= uint32(src[i])
	i += 1

	r = uint32(src[i]) << 24
	i += 1
	r |= uint32(src[i]) << 16
	i += 1
	r |= uint32(src[i]) << 8
	i += 1
	r |= uint32(src[i])
	i += 1

	x = ((l >> 4) ^ r) & 0x0f0f0f0f
	r ^= x
	l ^= x << 4
	x = ((l >> 16) ^ r) & 0x0000ffff
	r ^= x
	l ^= x << 16
	x = ((r >> 2) ^ l) & 0x33333333
	l ^= x
	r ^= x << 2
	x = ((r >> 8) ^ l) & 0x00ff00ff
	l ^= x
	r ^= x << 8
	r = (r << 1) | ((r >> 31) & 1)
	x = (l ^ r) & 0xaaaaaaaa
	l ^= x
	r ^= x
	l = (l << 1) | ((l >> 31) & 1)

	keysi := 0
	for i := 0; i < 8; i += 1 {
		x = ((r << 28) | (r >> 4)) ^ d.keys[keysi]
		keysi += 1
		fval := SP7[x&0x3f]
		fval |= SP5[(x>>8)&0x3f]
		fval |= SP3[(x>>16)&0x3f]
		fval |= SP1[(x>>24)&0x3f]
		x = r ^ d.keys[keysi]
		keysi += 1
		fval |= SP8[x&0x3f]
		fval |= SP6[(x>>8)&0x3f]
		fval |= SP4[(x>>16)&0x3f]
		fval |= SP2[(x>>24)&0x3f]
		l ^= fval
		x = ((l << 28) | (l >> 4)) ^ d.keys[keysi]
		keysi += 1
		fval = SP7[x&0x3f]
		fval |= SP5[(x>>8)&0x3f]
		fval |= SP3[(x>>16)&0x3f]
		fval |= SP1[(x>>24)&0x3f]
		x = l ^ d.keys[keysi]
		keysi += 1
		fval |= SP8[x&0x3f]
		fval |= SP6[(x>>8)&0x3f]
		fval |= SP4[(x>>16)&0x3f]
		fval |= SP2[(x>>24)&0x3f]
		r ^= fval
	}

	r = (r << 31) | (r >> 1)
	x = (l ^ r) & 0xaaaaaaaa
	l ^= x
	r ^= x
	l = (l << 31) | (l >> 1)
	x = ((l >> 8) ^ r) & 0x00ff00ff
	r ^= x
	l ^= x << 8
	x = ((l >> 2) ^ r) & 0x33333333
	r ^= x
	l ^= x << 2
	x = ((r >> 16) ^ l) & 0x0000ffff
	l ^= x
	r ^= x << 16
	x = ((r >> 4) ^ l) & 0x0f0f0f0f
	l ^= x
	r ^= x << 4

	xA := []uint32{r, l}
	for i := uint(0); i < 8; i++ {
		dst[i] = byte((xA[i>>2] >> (8 * (3 - (i % 4)))) % 256)
	}

	return nil
}

func New(password LossBytes) *DES {
	keys := make([]uint32, 32)
	pc1m := make([]uint32, 56)
	pcr := make([]uint32, 56)
	kn := make([]uint32, 32)

	for j, l := 0, 56; j < 56; j, l = j+1, l-8 {
		switch {
		case l < -5:
			l += 65
		case l < -3:
			l += 31
		case l < -1:
			l += 63
		case l == 27:
			l += 35
		}
		m := l & 0x7
		if password.Get(l>>3)&(1<<m) != 0 {
			pc1m[j] = 1
		} else {
			pc1m[j] = 0
		}
	}

	for i := 0; i < 16; i++ {
		m := i << 1
		n := m + 1
		kn[m], kn[n] = 0, 0
		for o := uint32(28); o < 59; o += 28 {
			for j := o - 28; j < o; j++ {
				l := j + totrot[i]
				if l < o {
					pcr[j] = pc1m[l]
				} else {
					pcr[j] = pc1m[l-28]
				}
			}
		}
		for j := 0; j < 24; j++ {
			if pcr[PC2[j]] != 0 {
				kn[m] |= 1 << uint(23-j)
			}
			if pcr[PC2[j+24]] != 0 {
				kn[n] |= 1 << uint(23-j)
			}
		}
	}

	// cookey
	rawi := 0
	KnLi := 0
	for i := 0; i < 16; i++ {
		raw0 := kn[rawi]
		rawi += 1
		raw1 := kn[rawi]
		rawi += 1

		keys[KnLi] = (raw0 & 0x00fc0000) << 6
		keys[KnLi] |= (raw0 & 0x00000fc0) << 10
		keys[KnLi] |= (raw1 & 0x00fc0000) >> 10
		keys[KnLi] |= (raw1 & 0x00000fc0) >> 6
		KnLi += 1

		keys[KnLi] = (raw0 & 0x0003f000) << 12
		keys[KnLi] |= (raw0 & 0x0000003f) << 16
		keys[KnLi] |= (raw1 & 0x0003f000) >> 4
		keys[KnLi] |= raw1 & 0x0000003f
		KnLi += 1
	}

	return &DES{
		keys: keys,
	}
}
