Skip to content

Commit

Permalink
js Transmission End
Browse files Browse the repository at this point in the history
  • Loading branch information
zhaouv committed Dec 8, 2023
1 parent a91b38a commit 242c65a
Show file tree
Hide file tree
Showing 4 changed files with 504 additions and 30 deletions.
247 changes: 221 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,253 @@
# Transfer-Data-Through-HDMI

Transfer data from a network-isolated system.
(Of course, USB storage devices are also prohibited)
Imagine a computer setup where the main unit is locked in a cabinet, and only the mouse, keyboard, and a monitor cable extend to the desktop through a small opening. The computer is not connected to the internet. How can the data from this computer be copied out?

... to be translated
For scenarios where the transmission cable can be unplugged, such as when using a non-customized monitor or when the monitor is not embedded in a cabinet, data can be rapidly transmitted using a video capture card.

To achieve this, open the browser's developer tools console on the computer and type the following code from [psend1.html](./psend1.html). Run [precieve1.py](./precieve1.py) on the video capture card to complete the process.

# 通过HDMI传输数据

从网络隔离的系统中获取数据.
(USB存储设备也无法使用)
假设有台这样的电脑摆在面前, 主机锁在柜子里, 只有鼠标键盘和一个显示器的线通过小孔拉到了桌面上, 电脑也没有连接到互联网, 如何把其中的数据复制出来.

对于没有定制显示器或者显示器没有嵌入到柜子中, 能够把传输线拔下来的情况, 通过视频采集卡能够高速的传输数据出来.

在这个电脑打开浏览器的开发者工具的console, 把[psend1.html](./psend1.html)中的如下部分敲进去执行, 视频采集卡运行[precieve1.py](./precieve1.py)即可完成.

```js
var width = 1920;
var height = 1080;
var useWidth = 1920 - 64;
var useHeight = 1080;
var useLeft = 0;
var useTop = 0;
var fpsSet = 4;
if (1) {
var d = 2;
var color = 1;
}
if (0) {
var d = 4;
var color = 3;
}

var devicePixelRatio = window.devicePixelRatio || 1;

function int2byte(number, length) {
var uint8Array = new Uint8Array(length);
for (var i = 0; i < length; i++) {
uint8Array[i] = Number((BigInt(number) >> BigInt(i * 8)) & BigInt(0xFF));
}
return uint8Array;
}

function byte2bit(byte, bit, offset = 0, reverse = false) {
for (var ni = 0; ni < byte.length; ni++) {
var bytei = byte[ni];
for (var bi = 0; bi < 8; bi++) {
bit[offset + ni * 8 + bi] = (bytei >> bi) & 1;
}
}
if (reverse) {
bit.subarray(offset, offset + 8*byte.length).reverse();
}
}

fileInput = document.createElement('input');
fileInput.type = 'file';
document.body.appendChild(fileInput);

fileInput.addEventListener('change', handleFileSelect);

canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.width = `${width/devicePixelRatio}px`;
canvas.style.height = `${height/devicePixelRatio}px`;
canvas.style.backgroundColor = 'black';
document.body.appendChild(canvas);

function toggleFullScreen() {
if (!document.fullscreenElement) {
canvas.requestFullscreen().catch(err => {
alert(`Error attempting to enable full-screen mode: ${err.message}`);
});
} else {
document.exitFullscreen();
}
}

canvas.addEventListener('dblclick', toggleFullScreen);

context = canvas.getContext('2d');

function renderFrame(count) {
console.log('renderFrame',count)
startt = Date.now();
xorFilePart = new Uint8Array([0, 0, 0, 0]);
xorHeadPart = new Uint8Array([255, 0, 0b01010101, 0b10101010]);

countB = int2byte(count, 4);
filePart = fileContent.slice(bytePerFrame * count, bytePerFrame * (count + 1));

effectiveByte = filePart.length;
if (filePart.length < bytePerFrame) {
filePart = new Uint8Array(bytePerFrame);
filePart.set(fileContent.subarray(bytePerFrame * count));
eof = true;
}

effectiveByteB = int2byte(effectiveByte, 4);

filePart.forEach((v,i)=>xorFilePart[i%4]^=v);

countB.forEach((v,i)=>xorHeadPart[i%4]^=v);
effectiveByteB.forEach((v,i)=>xorHeadPart[i%4]^=v);
fileSizeB.forEach((v,i)=>xorHeadPart[i%4]^=v);

rawdata = new Uint8Array((useHeight / d) * (useWidth / d) * color);

byte2bit(countB, rawdata, 0, true);
byte2bit(effectiveByteB, rawdata, 4 * 8, true);
byte2bit(fileSizeB, rawdata, (4 + 4) * 8, true);
byte2bit(xorHeadPart, rawdata, (4 + 4 + 8) * 8, true);
byte2bit(filePart, rawdata, (4 + 4 + 8 + 4) * 8);
byte2bit(xorFilePart, rawdata, (4 + 4 + 8 + 4 + bytePerFrame) * 8);

if (color==3) {
rawframe = rawdata.map(v=>v*255);
} else { //color==1
rawframe = new Uint8Array((useHeight / d) * (useWidth / d) * 3);
rawdata.forEach((v,i)=>rawframe[3*i]=rawframe[3*i+1]=rawframe[3*i+2]=v*255)
}

imageData = new Uint8ClampedArray(width * height * 4);
for (var x = 0; x < useWidth / d; x++) {
for (var y = 0; y < useHeight / d; y++) {
var frameIndex = (y * (useWidth / d) + x)*3;
for (var ii = 0; ii < d; ii++) {
for (var jj = 0; jj < d; jj++) {
var pixelIndex = ((useTop + jj + y*d) * width + useLeft + ii + x*d)*4;
imageData[pixelIndex] = rawframe[frameIndex];
imageData[pixelIndex + 1] = rawframe[frameIndex + 1];
imageData[pixelIndex + 2] = rawframe[frameIndex + 2];
imageData[pixelIndex + 3] = 255;
}
}
}
}

context.clearRect(0, 0, width, height);
context.putImageData(new ImageData(imageData, width, height), 0, 0);

fpsCount++;
if (fpsCount % 10 === 0) {
t2 = Date.now();
console.log(fpsCount, (t2 - t1)/1000, 1000*fpsCount / (t2 - t1));
}

分成以下几种设定
currentt = Date.now();
costt = currentt - startt;

是否允许接hdmi/dp线等
+ 是:通过采集卡传输图像
速度高且稳定, 不需要做位置识别之类的额外处理, 只需要对抗一下采集卡的压缩
+ 否:通过相机摄屏
要考虑对准以及颜色校准等等
setTimeout(function () {
if (count === fcount || eof) {
t2 = Date.now();
console.log(fpsCount, (t2 - t1)/1000, 1000*fpsCount / (t2 - t1));
return;
}

是否允许插自带的键盘鼠标
+ 是:通过被识别为hid设备的芯片来高速输入
通过CH340+CH9329伪装键鼠, 可以把复杂的纠错/压缩算法输入进去, 例如直接把二维码生成的算法放进去
+ 否:只能手动敲代码进去
此情况下内网端的代码必须简洁
renderFrame(count + 1);
}, Math.max(1, fpsDelay - costt));
}

作为合理的假设的话, 应该只要求内网有浏览器,
发送端应该是js脚本, 开发阶段内网也暂时用python3+numpy+cv2
同时先只考虑允许接hdmi使用采集卡的情况
function handleFileSelect(event) {
var fileInput = event.target;
var file = fileInput.files[0];

if (!file) {
return;
}

var reader = new FileReader();
reader.onload = function (e) {
fileContent = new Uint8Array(e.target.result);
showFileContent(fileContent);
};

reader.readAsArrayBuffer(file);
}

function showFileContent(fileContent) {
bytePerFrame = Math.ceil((useHeight * useWidth) / (d ** 2 * 8)) * color - (4 + 4 + 8 + 4 + 4);

fcount = Math.ceil(fileContent.length / bytePerFrame);
console.log('filesize', fileContent.length, 'fcount', fcount);

fileSizeB = int2byte(fileContent.length,8);

fpsDelay = Math.ceil(1000 / fpsSet);
fpsCount = 0;
eof = false;

toggleFullScreen()
setTimeout(() => {
t1 = Date.now();
renderFrame(0);
}, 5000);

}
```

## Protocol

For instance, using a region of 1920-64,1080, with the right side reserved for the mouse (64 width is removed for convenience):

The frame rate on the capture end needs to be greater than the output end, set at 4 frames/s.

Each point is duplicated into a 2x2 grid, storing 1 bit of information.

The protocol starts with 4 bytes indicating the current frame number, followed by 4 bytes for the effective byte count in the current frame, then 8 bytes indicating the total byte count, followed by 4 bytes for the XOR of the header section. After that comes the data section, and after the effective length, 4 more bytes are appended to calculate the XOR of the data section.

`((1920-64)/2)*(1080/2)*1/8-(4+4+8+4+4)=62616`

| Byte Count | 4 | 4 | 8 | 4 | 62616 | 4 |
|------------|---|---|---|---|-------|---|
| Content | Current Frame | Effective Byte Count | Total Byte Count | Header XOR | File Content | Content XOR |

## 协议

暂定使用 1920-64,1080 的区域, 右边用来放鼠标, (为了方便直接切掉了64宽度的一整条)
例如使用 1920-64,1080 的区域, 右边用来放鼠标, (为了方便直接切掉了64宽度的一整条)

采集端的帧率需要大于输出端, 暂定为4帧/s

每个点重复成22的格子 ~ [11 22 44 88]

每个点储存1比特的信息 ~ [1 3 6 9 12 15 18 21 24]
每个点重复成2x2的格子, 储存1比特的信息

开头4字节当前第几帧 再4字节本帧的有效字节数 再8字节表明总字节数 再4字节head部分的xor 然后是数据部分 有效长度之后再补4字节用来算数据部分的xor
开头4字节当前第几帧, 再4字节本帧的有效字节数, 再8字节表明总字节数, 再4字节head部分的xor, 然后是数据部分, 有效长度之后再补4字节用来算数据部分的xor

`((1920-64)/2)*(1080/2)*1/8-(4+4+8+4+4)=62616`

|字节数|4|4|8|4|62616|4|
|-|-|-|-|-|-|-|
|内容|当前帧数|本帧有效字节数|文件总字节数|协议头的校验|文件内容|文件内容的校验|

## Implementation

Currently, a simplified implementation has been done for storing 1 bit in a 2x2 grid and 3 bits in a 4x4 grid. The cheapest HDMI in + HDMI out + USB 3.0 capture card, priced at RMB129, has considerable compression, reaching this level, and the fps can only go up to 5. For the combination of storing 3 bits in a 4x4 grid, each frame's information is 3/4 of storing 1 bit in a 2x2 grid.

[Transmission End (JavaScript)](psend1.html) or [Transmission End (Python for debugging)](psend1.py)
[Reception End](precieve1.py)

## 实现

目前简略实现了 22的格子储存1比特44的格子储存3比特 ,最便宜的 hdmi in + hdmi out + usb3.0 的 RMB129的采集卡压缩的比较多, 只能到这种程度了, 而且fps只能到5. 44的格子储存3比特 的组合的话每帧的信息是 22的格子储存1比特 的3/4.
目前简略实现了 2x2的格子储存1比特4x4的格子储存3比特 ,最便宜的 hdmi in + hdmi out + usb3.0 的 RMB129的采集卡, 压缩的比较多, 只能到这种程度了, 而且fps只能到5. 4x4的格子储存3比特 的组合的话每帧的信息是 2x2的格子储存1比特 的3/4.

[发送端](psend1.py)
发送端[js](psend1.html) 或者 [py(用于调试)](psend1.py)
[接收端](precieve1.py)

## Results

Successfully transmitted a 60,448,768-byte file in 247 seconds.

## 效果

247秒正确传输了一个60448768字节的文件
Expand Down
6 changes: 4 additions & 2 deletions precieve1.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def single(frame):
height=1080
use_width=1920-64
use_height=1080
use_left=0
use_top=0
case=0
if case==0:
d=2
Expand All @@ -86,9 +88,9 @@ def eachframe(frame):
for jj in range(d):
if color==1:
for kk in range(3):
tosum+=frame[ii:use_height:d,jj:use_width:d,kk:kk+1]
tosum+=frame[ii+use_top:use_height+use_top:d,jj+use_left:use_width+use_left:d,kk:kk+1]
elif color==3:
tosum+=frame[ii:use_height:d,jj:use_width:d,:]
tosum+=frame[ii+use_top:use_height+use_top:d,jj+use_left:use_width+use_left:d,:]
else:
raise RuntimeError('color should be 1 or 3')
retpic = (tosum/(d*d*3/color)).astype("uint8")
Expand Down
Loading

0 comments on commit 242c65a

Please sign in to comment.