File size: 10,836 Bytes
783e73e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b4d39de
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>CVNSS4.0 ⇄ Bila Hex/Nhị phân | Chưa hỗ trợ chuỗi dài</title>
  <style>
    body { background: #1e2232; font-family: 'Fira Mono', monospace; margin: 0; }
    .container { max-width: 760px; margin: 44px auto; background: #181f29; border-radius: 16px;
      box-shadow: 0 3px 18px #000c; padding: 32px 24px 24px 24px;}
    h2 { color: #e0e7ef; font-weight: 500; margin-bottom: 8px;}
    label { font-weight: bold; color: #38bdf8; margin-top: 12px; display: block;}
    textarea { width: 100%; font-size: 1em; background: #1e293b; color: #f1f5f9;
      border-radius: 7px; padding: 12px; border: 1px solid #334155;
      margin-bottom: 14px; min-height: 48px; resize: vertical;}
    button { background: #38bdf8; color: #020617; border: none; border-radius: 8px;
      padding: 11px 20px; font-size: 1em; font-family: inherit; font-weight: 600;
      margin: 7px 13px 7px 0; box-shadow: 0 1px 5px #0891b2aa; transition: 0.12s;}
    button:hover { background: #0ea5e9; color: #fff;}
    .outblock { background: #222b38; border-radius: 8px; margin-top: 12px; margin-bottom: 10px; }
    .outlabel { color: #6ee7b7; font-size: 0.99em; padding: 8px 0 2px 2px; }
    .output { color: #e5e5e5; background: none; padding: 8px 8px 4px 8px; font-size: 1.08em;
      word-break: break-all; font-family: 'Fira Mono', monospace; min-height: 16px;}
    .error { color: #f87171; font-weight: bold; padding: 7px 0 0 0;}
    .note { color: #a5b4fc; font-size: 0.97em; margin-top: 12px;}
    .byline { text-align: right; margin-top: 26px; color: #64748b; font-size: 0.97em;}
    @media (max-width: 800px) { .container {padding: 10px; max-width: 99vw;} }
    ::selection { background: #38bdf888; }
  </style>
</head>
<body>
  <div class="container">
    <h2>CVNSS4.0 ⇄ Bila Hex/Nhị phân<br> Hiện chỉ hỗ trợ khoảng 30 ký tự để test</h2>

    <label for="ascii">CVNSS4.0:</label>
    <textarea id="ascii" placeholder="Nhập chữ cái, số, dấu cách... Chuỗi dài cỡ 50 ký tự đều được!"></textarea>
    <button onclick="asciiToCBOR()">CVNSS4.0 → Bila</button>
    <button onclick="clearAll()">Xoá</button>
    <div class="error" id="err_ascii"></div>

    <div class="outblock">
      <div class="outlabel">Bila Hex:</div>
      <div class="output" id="cborhex"></div>
      <div class="outlabel">Bila Nhị phân:</div>
      <div class="output" id="cborbin"></div>
    </div>

    <label for="cborhex_in">Bila Hex:</label>
    <textarea id="cborhex_in" placeholder="Nhập mã Hex hợp lệ (header 78xx, 79xxxx, ... không giới hạn độ dài)"></textarea>
    <button onclick="cborHexToAscii()">Bila Hex → CVNSS4.0</button>
    <div class="error" id="err_hex"></div>

    <label for="cborbin_in">Bila Nhị phân:</label>
    <textarea id="cborbin_in" placeholder="Nhập mã Nhị phân hợp lệ"></textarea>
    <button onclick="cborBinToAscii()">Bila Nhị phân → CVNSS4.0</button>
    <div class="error" id="err_bin"></div>

    <div class="outblock">
      <div class="outlabel">Kết quả:</div>
      <div class="output" id="ascii_out"></div>
    </div>

    <div class="note">
      • Nhập/dán tối đa 30-40 ký tự.<br>
      • Tự động loại ký tự không hợp lệ (chỉ giữ A–Z, a–z, 0–9, dấu cách).<br>
      • Mã Hex dài sẽ tự động dùng header 78xx, 79xxxx theo chuẩn RFC7049.<br>
      • Kết quả chưa tương thích sẽ báo lỗi, lấy ký tự CVNSS4.0 (<a href="https://chuvnsongsong.com/" target="_blank" style="color:#38bdf8">Chữ VN Song Song 4.0</a>).
    </div>
    <div class="byline">© Long Ngo, 2025 – Bản demo đang thử nghiệm</div>
  </div>
  <script>
    // Chặn realtime ký tự lạ khi gõ/dán vào textarea (giữ input sạch)
    document.getElementById('ascii').addEventListener('input', function() {
      let clean = this.value.replace(/[^A-Za-z0-9 ]+/g, '');
      if(this.value !== clean) {
        this.value = clean;
        document.getElementById('err_ascii').textContent = '❌ Đã tự động loại bỏ ký tự không hợp lệ!';
      } else {
        document.getElementById('err_ascii').textContent = '';
      }
    });

    function cleanASCII(s) {
      return s.replace(/[\r\n\t\u200B-\u200D\uFEFF]/g, "");
    }
    function checkValidASCII(s) {
      return /^[A-Za-z0-9 ]+$/.test(s);
    }

    // Hỗ trợ CBOR chuẩn mọi độ dài: <24, <256, <65536 ký tự
    function asciiToCBOR() {
      let t = document.getElementById('ascii').value;
      document.getElementById('err_ascii').textContent = "";
      if (!t) return showCBOR('', '');
      let tclean = cleanASCII(t);
      if (!checkValidASCII(tclean)) {
        let wrong = tclean.replace(/[A-Za-z0-9 ]/g, "");
        document.getElementById('err_ascii').textContent =
          '❌ Chỉ cho phép A–Z, a–z, 0–9, dấu cách! Ký tự lỗi: [' + wrong.split("").join(" ") + ']';
        showCBOR('', '');
        return;
      }
      let encoder = new TextEncoder();
      let bytes = encoder.encode(tclean);
      let hex = "", b = "";
      if (bytes.length < 24) {
        hex = (0x60 + bytes.length).toString(16);
      } else if (bytes.length < 256) {
        hex = "78" + bytes.length.toString(16).padStart(2, "0");
      } else if (bytes.length < 65536) {
        hex = "79" + bytes.length.toString(16).padStart(4, "0");
      } else {
        document.getElementById('err_ascii').textContent = '❌ Chuỗi quá dài (tối đa 65.535 ký tự)!';
        showCBOR('', '');
        return;
      }
      hex += Array.from(bytes).map(x => x.toString(16).padStart(2, "0")).join('');
      b = hex.match(/.{1,2}/g).map(x => parseInt(x, 16).toString(2).padStart(8, '0')).join('');
      showCBOR(hex, b);
    }

    function cborHexToAscii() {
      let hex = document.getElementById('cborhex_in').value
        .replace(/[^0-9a-fA-F]/g, '')
        .toLowerCase();
      document.getElementById('err_hex').textContent = "";
      if (!hex) return showAscii('');
      if (hex.length % 2 !== 0) {
        document.getElementById('err_hex').textContent = '❌ Hex phải có số ký tự chẵn!';
        showAscii('');
        return;
      }
      if (!/^[0-9a-f]+$/.test(hex)) {
        document.getElementById('err_hex').textContent = '❌ Hex không hợp lệ!';
        showAscii('');
        return;
      }
      try {
        let idx=0, l=0, fb=parseInt(hex.substr(idx,2),16); idx+=2;
        if ((fb&0xe0)===0x60) l=fb&0x1f;
        else if(fb===0x78) { l=parseInt(hex.substr(idx,2),16); idx+=2; }
        else if(fb===0x79) { l=parseInt(hex.substr(idx,4),16); idx+=4; }
        else { document.getElementById('err_hex').textContent = '❌ Không đúng mã string'; showAscii(''); return; }
        let bh = hex.substr(idx, l*2);
        if (bh.length<l*2) {
          document.getElementById('err_hex').textContent = '❌ Thiếu bytes, cần ' + (l*2) + ' ký tự hex, chỉ có ' + bh.length;
          showAscii('');
          return;
        }
        let bs=[];
        for(let i=0;i<bh.length;i+=2) bs.push(parseInt(bh.substr(i,2),16));
        let s;
        try {
          s = new TextDecoder().decode(new Uint8Array(bs));
        } catch (e) {
          document.getElementById('err_hex').textContent = '❌ Không thể giải mã UTF-8: ' + e.message;
          showAscii('');
          return;
        }
        let clean = cleanASCII(s);
        if (!checkValidASCII(clean)) {
          let wrong = clean.replace(/[A-Za-z0-9 ]/g, "");
          document.getElementById('err_hex').textContent =
            '❌ Dữ liệu nhập không hợp lệ (chỉ cho A–Z, a–z, 0–9, dấu cách)! Ký tự lỗi: [' +
            wrong.split("").join(" ") + ']';
          showAscii('');
          return;
        }
        showAscii(clean);
      } catch (err) {
        document.getElementById('err_hex').textContent = '❌ Lỗi giải mã: ' + err.message;
        showAscii('');
      }
    }

    function cborBinToAscii() {
      let bin = document.getElementById('cborbin_in').value.trim().replace(/\s+/g,'');
      document.getElementById('err_bin').textContent = "";
      if (!bin) return showAscii('');
      if (!/^[01]+$/.test(bin)) { document.getElementById('err_bin').textContent = '❌ Nhị phân không hợp lệ!'; showAscii(''); return; }
      if (bin.length%8!==0) { document.getElementById('err_bin').textContent = '❌ Chuỗi phải là bội số 8!'; showAscii(''); return; }
      try {
        let idx=0, l=0, fb=parseInt(bin.substr(idx,8),2); idx+=8;
        if ((fb&0xe0)===0x60) l=fb&0x1f;
        else if(fb===0x78) { l=parseInt(bin.substr(idx,8),2); idx+=8; }
        else if(fb===0x79) { l=parseInt(bin.substr(idx,16),2); idx+=16; }
        else { document.getElementById('err_bin').textContent = '❌ Không đúng CBOR string'; showAscii(''); return; }
        let bs=[];
        for(let i=0;i<l;i++) {
          if(idx+8>bin.length) { document.getElementById('err_bin').textContent = '❌ Thiếu bytes'; showAscii(''); return; }
          bs.push(parseInt(bin.substr(idx,8),2)); idx+=8;
        }
        let s;
        try {
          s = new TextDecoder().decode(new Uint8Array(bs));
        } catch (e) {
          document.getElementById('err_bin').textContent = '❌ Không thể giải mã UTF-8: ' + e.message;
          showAscii('');
          return;
        }
        let clean = cleanASCII(s);
        if (!checkValidASCII(clean)) {
          let wrong = clean.replace(/[A-Za-z0-9 ]/g, "");
          document.getElementById('err_bin').textContent =
            '❌ Dữ liệu nhập không hợp lệ (chỉ cho A–Z, a–z, 0–9, dấu cách)! Ký tự lỗi: [' +
            wrong.split("").join(" ") + ']';
          showAscii('');
          return;
        }
        showAscii(clean);
      } catch (err) {
        document.getElementById('err_bin').textContent = '❌ Lỗi giải mã: ' + err.message;
        showAscii('');
      }
    }

    function showCBOR(hex, bin) {
      document.getElementById('cborhex').textContent = hex;
      document.getElementById('cborbin').textContent = bin;
      document.getElementById('ascii_out').textContent = "";
    }
    function showAscii(ascii) {
      document.getElementById('ascii_out').textContent = ascii;
    }
    function clearAll() {
      document.getElementById('ascii').value = '';
      document.getElementById('cborhex_in').value = '';
      document.getElementById('cborbin_in').value = '';
      document.getElementById('err_ascii').textContent = '';
      document.getElementById('err_hex').textContent = '';
      document.getElementById('err_bin').textContent = '';
      showCBOR('', '');
      showAscii('');
    }
  </script>
</body>
</html>