# bites

This package provides a useful class: `Bs` (Bytes), to help you do several operations on bytes without writing a lot of stupid codes.

## Install

```python3 -m pip install -U bites
```

## Why?

When you want to conduct a simple xor

```a = b'deadbeef'
b = b'faceb00k'

# Standard
c = bytes(i ^ j for i, j in zip(a, b))

# NumPy
import numpy as np
c = (np.array([*a]) ^ np.array([*b])).astype(np.uint8).tobytes()

# Using this package
from bites import Bs
c = bytes(Bs(a) ^ b)
```

When you need a simple encryption

```m = 'the_plain_text_you_wanna_encrypt'
e = 'the_secret_key'

# Standard
mb = m.encode()
eb = e.encode()
mbl = len(mb)
ebl = len(eb)
l = max(mbl, ebl)
cb = bytes([mb[i % mbl] ^ eb[i % ebl] for i in range(l)])

# NumPy
import numpy as np
mb = np.array(list(m.encode()))
eb = np.array(list(e.encode()))
print('You should repeat these arrays to the same length to use xor, '
"and you gived up just because you don't know which method to use.\n"
'You start googling then write down this code:')
eb_ = np.tile(eb, mb.size // eb.size + 1)[:mb.size]
cb = (mb ^ eb_).astype(np.uint8).tobytes() # elegant!

# After you found this package:
from bites import Bs
cb = bytes(Bs(m) ^ e)

# Or, if you don't want auto repeating / padding, use n()
cb = bytes(Bs(m) ^ Bs(e).n())   # error!!!
cb = bytes(Bs(m) ^ Bs(e).r())   # repeat e to fit the length of m
cb = bytes(Bs(m) ^ Bs(e).p(0))  # pad e with 0s to fit the length of m
```
• `Bs` by default uses auto repeating.
• `bs.r()` explicitly specifies using auto repeating.
• `bs.p(c)` returns a `Bs` that pads `c` to fit the other longer operands by default.
• `bs.n()` returns a `Bs` object that will not change its length automatically.

## Usage

```from bites import Bs

bs = Bs(1, 2, 3, 4)
bs # <Bs 4: [01, 02, 03, 04]>
```

### Creating `Bs`

These lines create the same thing, the input parameters will be flattened and automatically converted to ints in `range(0, 256)`.

```# These all create `<Bs 4: [01, 02, 03, 04]>`
Bs(1, 2, 3, 4)
Bs([1, 2, 3, 4])
Bs([[1], [2], [3, [4]]])
Bs(256+1, 256+2, 256+3, 256+4)
Bs(1-256, 2-256, 3-256, 4-256)
Bs(bytes([1, 2]), 3, 4)
Bs('\x01', b'\x02', 3, [4])
Bs(Bs(1, 2), [3], 4)
Bs(Bs(1, Bs(2)), Bs([3, Bs(4)]))
```

Simple rules

• `int` will be replaced with its remainder of 256.
• `str` will be encoded into `bytes` (UTF-8).
• `Iterable` will be flattened.
```>>> Bs(range(5))
<Bs 5: [00, 01, 02, 03, 04]>

>>> Bs([i for i in range(256) if i % 3 == i % 7 == 0 ])
<Bs 13: [00, 15, 2a, 3f, 54, 69, 7e, 93, a8, bd, d2, e7, fc]>

>>> Bs(map(lambda n: n + 3, range(5)))
<Bs 5: [03, 04, 05, 06, 07]>

>>> Bs(range(0, 3), range(10, 13))
<Bs 6: [00, 01, 02, 0a, 0b, 0c]>
```

From integer

```# Integers will be considered as little endien
>>> Bs.from_int(8192)
<Bs 2: [00, 20]>
>>> Bs.from_int(0x102030)
<Bs 3: [30, 20, 10]>

# Simply call `bs.rev()` if you want big endian
>>> Bs.from_int(8192).rev()
<Bs 2: [20, 00]>
```

From hex string (bytewise)

```# 'DE' is the first byte
<Bs 4: [de, ad, be, ef]>

# If the string starts with '0x', 'EF' will be the first byte
<Bs 4: [ef, be, ad, de]>
<Bs 4: [ef, be, ad, de]>
```

From bit string (bitwise)

```# The first bit is LSB
>>> Bs.from_bin('00001111')
<Bs 1: [f0]>

# If the string starts with '0b', the first bit in the string is MSB
>>> Bs.from_bin('0b00001111')
<Bs 1: [0f]>
>>> Bs.from_int(int('0b00001111', 2))
<Bs 1: [0f]>

# Notice that this will not be '00001111'
>>> Bs.from_bin('0b00001111').bin()
'11110000'
```

From file

```print(Bs.load('/etc/passwd').str())
```

From base64 encoded bytes

```Bs.from_base64('ZnVjaw==')
```

Random

```>>> import string

>>> Bs.rand(8, cs=(string.ascii_lowercase + '0123456'))
<Bs 8: [7a, 6a, 32, 72, 71, 6c, 68, 33]>

>>> Bs.rand(8, cs=range(100))
<Bs 8: [35, 44, 4a, 5a, 06, 2d, 5a, 38]>

>>> Bs.rand(8, cs=string.hexdigits)
<Bs 8: [45, 31, 34, 65, 45, 62, 34, 62]>
```

### Basic Operations

Slicing

```>>> bs = Bs(1, 2, 3, 4)
>>> bs
<Bs 4: [01, 02, 03, 04]>

>>> bs[:2]
<Bs 2: [01, 02]>

>>> bs[2]
<Bs 1: [03]>

>>> bs[-1]
<Bs 1: [04]>

>>> bs[::-1]
<Bs 4: [04, 03, 02, 01]>

>>> bs[::2]
<Bs 2: [01, 03]>
```

Setting values for slice of `Bs`

```>>> bs = Bs(1, 2, 3, 4)
>>> bs
<Bs 4: [01, 02, 03, 04]>

>>> bs[:2] = 0
>>> bs
<Bs 4: [00, 00, 03, 04]>

>>> bs[:] = 0
>>> bs
<Bs 4: [00, 00, 00, 00]>

>>> bs[:] = '1234'
>>> bs
<Bs 4: [31, 32, 33, 34]>

>>> bs[:] = '123'
>>> bs
<Bs 4: [31, 32, 33, 31]>

>>> bs[:] = '12345'
>>> bs
<Bs 4: [31, 32, 33, 34]>

>>> bs[:] = Bs('12').n()
# Error: cannot set values to range(0, 4): r=False, p=None, l=2

>>> bs[:] = Bs('12').p(0)

>>> bs
<Bs 4: [31, 32, 00, 00]>
```

Useful methods

```>>> bs = Bs('dead')
>>> bs
<Bs 4: [64, 65, 61, 64]>

# Repeat n times
>>> bs.rep(2)
<Bs 8: [64, 65, 61, 64, 64, 65, 61, 64]>

# Repeat to length
>>> bs.repto(6)
<Bs 6: [64, 65, 61, 64, 64, 65]>

<Bs 6: [64, 65, 61, 64, 00, 00]>

# Append or concatenate
>>> bs @ 'beef'
<Bs 8: [64, 65, 61, 64, 62, 65, 65, 66]>

# Extend to length automatically
>>> bs.extto(6)
<Bs 6: [64, 65, 61, 64, 64, 65]>

# Explicit automatic repeating
>>> bs.r().extto(6)
<Bs 6: [64, 65, 61, 64, 64, 65]>

>>> bs.p(0).extto(6)
<Bs 6: [64, 65, 61, 64, 00, 00]>

# Disable automatic extension
>>> bs.n().extto(6) # Error
```

### Bytewise Operations

Operends with `Bs` objects will first be converted into `Bs`. If the lengths don't match, the shorter one will call `shorter_bs.extto(len(longer_bs))` to fit the longer operand's length.

```>>> a = Bs.from_int(0x0a00)
>>> a
<Bs 2: [00, 0a]>

>>> b = Bs.from_int(0x0b)
>>> b
<Bs 1: [0b]>

>>> a + b # b will be unrolled to <Bs 2: [0b, 0b]>
<Bs 2: [0b, 15]>

>>> a + b.n()
# Error: length not matched: (2, 1)

>>> a - b
<Bs 2: [f5, ff]>

>>> a * b
<Bs 2: [00, 6e]>

>>> a / b
<Bs 2: [00, 00]>

>>> a // b
<Bs 2: [00, 00]>

>>> a ** b
<Bs 2: [00, 00]>

>>> a % b
<Bs 2: [00, 0a]>
```

Operating `Bs` with other types:

```>>> cafe = Bs.from_hex('c01dcafe')
>>> cafe
<Bs 4: [c0, 1d, ca, fe]>

>>> cafe + 1
<Bs 4: [c1, 1e, cb, ff]>

>>> 1 + cafe
<Bs 4: [c1, 1e, cb, ff]>

>>> cafe + '糖'
<Bs 4: [a7, d0, 60, e5]>

>>> cafe + b'cafe'
<Bs 4: [23, 7e, 30, 63]>

>>> cafe + b'sugar'
<Bs 5: [33, 92, 31, 5f, 32]>

>>> cafe + [1, 2, 3, 4]
<Bs 4: [c1, 1f, cd, 02]>

>>> cafe + range(5)
<Bs 5: [c0, 1e, cc, 01, c4]>

>>> cafe.p(0) + [0] * 6
<Bs 6: [c0, 1d, ca, fe, 00, 00]>

>>> cafe.bin()
'00000011101110000101001101111111'

>>> (cafe >> 1).bin() # for each byte
'00000110011100001010011011111110'

>>> (cafe << 1).bin() # for each byte
'00000001010111000010100100111111'
```

Other useful methods

```>>> bs = Bs(range(7))
>>> bs
<Bs 7: [00, 01, 02, 03, 04, 05, 06]>

>>> bs.every(3)
[<Bs 3: [00, 01, 02]>, <Bs 3: [03, 04, 05]>, <Bs 1: [06]>]

>>> bs.every(3, list)
[[0, 1, 2], [3, 4, 5], [6]]

>>> bs.every(3, int)
[131328, 328707, 6]

>>> bs.every(4, lambda i: i.asint(32)) # with map
[50462976, 394500]

>>> bs.every(4, list, lambda i: 2 in i) # filter before map
[[0, 1, 2, 3]]

>>> bs.every(4, c=lambda i: 2 in i) # only filter
[<Bs 4: [00, 01, 02, 03]>]

>>> bs.rev()
<Bs 7: [06, 05, 04, 03, 02, 01, 00]>

>>> bs.roll(1)
<Bs 7: [06, 00, 01, 02, 03, 04, 05]>

>>> bs.roll(-1)
<Bs 7: [01, 02, 03, 04, 05, 06, 00]>

>>> bs.rjust(10, 0xff)
<Bs 10: [ff, ff, ff, 00, 01, 02, 03, 04, 05, 06]>

>>> bs.ljust(10, 0xff)
<Bs 10: [00, 01, 02, 03, 04, 05, 06, ff, ff, ff]>
```

### Bitwise Operations

Basic properties

```#                       v MSB          v LSB
>>> bs = Bs.from_bin('0b1111000011001100')

>>> bs.bin()
'0011001100001111'

>>> bin(bs)
'0b1111000011001100'

>>> bs
<Bs 2: [cc, f0]>

>>> bs.int()
61644

#                     v LSB          v MSB
>>> bs = Bs.from_bin('1111000011001100')

>>> bs.bin()
'1111000011001100'

>>> bin(bs)
'0b11001100001111'

>>> bs
<Bs 2: [0f, 33]>

>>> bs.int()
13071
```

Logical operations

```>>> x = Bs.from_bin('1111000010101010')

>>> (~x).bin()
'0000111101010101'

>>> y = Bs.from_bin('1' * 16)

>>> (x & y).bin()
'1111000010101010'

>>> (x | y).bin()
'1111111111111111'

>>> (x ^ y).bin()
'0000111101010101'
```

Shifting all bits

```>>> bs = Bs.from_bin('1100000000000001')

>>> bs.shift(1).bin()
'0110000000000000'

>>> bs.shift(-1).bin()
'1000000000000010'

>>> bs.asint()
-32765

>>> bs.shift(-1, a=True).bin() # arithmetic
'1000000000000011'

>>> bs.shift(-2, a=True).bin()
'0000000000000111'

>>> bs.shift(-5, a=True).bin()
'0000000000111111'

>>> bs.shift(-100, a=True).bin()
'1111111111111111'

>>> bs = Bs.from_bin('0000000000000010')

>>> bs.asint()
16384

>>> bs.shift(1, a=True).bin()
'0000000000000000'

>>> bs.shift(1, a=True).asint()
0

>>> bs.shift(-1, a=True).bin()
'0000000000000100'

>>> bs.shift(-1, a=True).asint()
8192

>>> bs.shift(-5, a=True).bin()
'0000000001000000'

>>> bs.shift(-5, a=True).asint()
512

>>> bs.shift(-100, a=True).bin()
'0000000000000000'

>>> bs.shift(-100, a=True).asint()
0
```

Other useful methods

```>>> bs = Bs.from_bin('1100000000000001')

>>> bs.revbits().bin()
'1000000000000011'

>>> bs.rollbits(1).bin()
'1110000000000000'

>>> bs.rollbits(-1).bin()
'1000000000000011'

>>> bs.bits()
['1', '1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1']

>>> bs.bits(every=3)
['110', '000', '000', '000', '000', '1']

>>> [b for b in bs.bits(every=3) if len(b) == 3]
['110', '000', '000', '000', '000']
```

### Convertions

To integer

```>>> bs = Bs(0, 0b10000000)
>>> bs
<Bs 2: [00, 80]>

>>> bs.int()
32768

>>> int(bs)
32768

>>> bs.asint()
-32768

>>> bs.asint(8)
0

>>> bs.asint(16)
-32768

>>> bs.asint(32)
32768

>>> bs.asuint()
32768

>>> bs.asuint(8)
0
```

To string

```>>> bs = Bs('las vegas')
>>> bs
<Bs 9: [6c, 61, 73, 20, 76, 65, 67, 61, 73]>

>>> bs.str()
'las vegas'

>>> str(bs)
'las vegas'

>>> bs.hex()
'6c6173207665676173'

>>> hex(bs)
'0x73616765762073616c'

>>> oct(bs)
'0o346605473127304034660554'

>>> bs.bin()
'001101101000011011001110000001000110111010100110111001101000011011001110'

>>> bin(bs)
'0b11100110110000101100111011001010111011000100000011100110110000101101100'
```

### Other

```>>> bs = Bs('Las Vegas')
>>> bs
<Bs 9: [4c, 61, 73, 20, 56, 65, 67, 61, 73]>

# Base64 encode
>>> bs.base64()
'TGFzIFZlZ2Fz'

# Hash
>>> bs.hash('md5')
<Bs 16: [05, c2, 7b, f0, 09, 32, 57, 2d, e2, 8b, f6, 5a, 05, 39, ba, 97]>

>>> bs.hash('md5').hex()
'05c27bf00932572de28bf65a0539ba97'

>>> bs.hash('sha256')
<Bs 32: [2b, d2, 5c, d9, 60, ab, a8, b7, 06, e2, b6, 7f, 2b, b3, 8b, 75, 0e, e5, 38, 4b, 0e, 98, 83, 05, 3e, bc, 3b, 89, ef, 4d, de, f9]>

>>> bs.hash('sha256').hex()
'2bd25cd960aba8b706e2b67f2bb38b750ee5384b0e9883053ebc3b89ef4ddef9'

# See what's available
>>> import hashlib
>>> hashlib.algorithms_guaranteed
{'sha384', 'shake_128', 'sha3_256', 'sha3_512', 'md5', 'sha512', 'shake_256', 'sha3_384', 'sha1', 'sha3_224', 'blake2b', 'blake2s', 'sha256', 'sha224'}
```