summaryrefslogtreecommitdiff
path: root/source/util/Bytes.mc
blob: 1008fd2323d92834789897e9a746f926f81d41cf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Author:  cirakg & claude ai
// Summary: Byte-array helpers: hex and UTF-8 conversion, concatenation, wiping and constant-time comparison.
//
// Security notes:
//   * wipe() overwrites a buffer in place. Call it on every buffer that held a
//     key or a decrypted secret as soon as it is no longer needed. Monkey C
//     can't guarantee no other copy exists (README 7.8), so this is best effort.
//   * equal() always looks at every byte, so comparing a MAC doesn't reveal
//     through its running time how many leading bytes matched.

import Toybox.Lang;
import Toybox.StringUtil;

module Bytes {

    // Hex text (any case, even length) to bytes. Empty text gives an empty array.
    function fromHex(hex as String) as ByteArray {
        if (hex.length() == 0) {
            return []b;
        }
        return StringUtil.convertEncodedString(hex, {
            :fromRepresentation => StringUtil.REPRESENTATION_STRING_HEX,
            :toRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY
        }) as ByteArray;
    }

    // Bytes to lowercase hex text.
    function toHex(data as ByteArray) as String {
        if (data.size() == 0) {
            return "";
        }
        return (StringUtil.convertEncodedString(data, {
            :fromRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
            :toRepresentation => StringUtil.REPRESENTATION_STRING_HEX
        }) as String).toLower();
    }

    // The UTF-8 bytes of a string.
    function fromUtf8(text as String) as ByteArray {
        if (text.length() == 0) {
            return []b;
        }
        return StringUtil.convertEncodedString(text, {
            :fromRepresentation => StringUtil.REPRESENTATION_STRING_PLAIN_TEXT,
            :toRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
            :encoding => StringUtil.CHAR_ENCODING_UTF8
        }) as ByteArray;
    }

    // A new array holding a followed by b; neither input is changed.
    function concat(a as ByteArray, b as ByteArray) as ByteArray {
        var out = new [0]b;
        out.addAll(a);
        out.addAll(b);
        return out;
    }

    // A new array with a copy of data.
    function copy(data as ByteArray) as ByteArray {
        var out = new [0]b;
        out.addAll(data);
        return out;
    }

    // Overwrite every byte with zero. Accepts null so callers can wipe fields
    // that may not have been set.
    function wipe(data as ByteArray?) as Void {
        if (data == null) {
            return;
        }
        for (var i = 0; i < data.size(); i++) {
            data[i] = 0;
        }
    }

    // True when both arrays have the same length and content. The running time
    // depends only on the length, not on where the first difference is.
    function equal(a as ByteArray, b as ByteArray) as Boolean {
        if (a.size() != b.size()) {
            return false;
        }
        var diff = 0;
        for (var i = 0; i < a.size(); i++) {
            diff = diff | (a[i] ^ b[i]);
        }
        return diff == 0;
    }

    // XOR source into target, byte by byte (same length required).
    function xorInto(target as ByteArray, source as ByteArray) as Void {
        for (var i = 0; i < target.size(); i++) {
            target[i] = target[i] ^ source[i];
        }
    }

    // An unsigned 64-bit value as 8 bytes, most significant first (the HOTP
    // counter encoding of RFC 4226).
    function uint64BigEndian(value as Long) as ByteArray {
        var out = new [8]b;
        var v = value;
        for (var i = 7; i >= 0; i--) {
            out[i] = (v & 0xFFl).toNumber();
            v = v >> 8;
        }
        return out;
    }

    // An unsigned 32-bit value as 4 bytes, most significant first.
    function uint32BigEndian(value as Number) as ByteArray {
        var out = new [4]b;
        out.encodeNumber(value, Lang.NUMBER_FORMAT_UINT32, {:offset => 0, :endianness => Lang.ENDIAN_BIG});
        return out;
    }
}