package com.ttstd.adbloopback.adb;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.zip.CRC32;

/**
 * ADB 消息封装与读写。
 *
 * <pre>
 * struct message {
 *     unsigned command;       // 命令
 *     unsigned arg0;          // 第一参数（本地 id）
 *     unsigned arg1;          // 第二参数（远端 id）
 *     unsigned data_length;   // 负载长度
 *     unsigned data_crc32;    // 负载 CRC32
 *     unsigned magic;         // command ^ 0xffffffff
 * };
 * </pre>
 * 所有整型字段均为小端序（little-endian）。
 */
public class AdbMessage {
    public int command;
    public int arg0;
    public int arg1;
    public byte[] data;

    public AdbMessage(int command, int arg0, int arg1, byte[] data) {
        this.command = command;
        this.arg0 = arg0;
        this.arg1 = arg1;
        this.data = data != null ? data : new byte[0];
    }

    public static void write(OutputStream out, AdbMessage msg) throws IOException {
        byte[] payload = msg.data;
        int crc = crc32(payload);
        ByteBuffer buf = ByteBuffer.allocate(24 + payload.length);
        buf.order(ByteOrder.LITTLE_ENDIAN);
        buf.putInt(msg.command);
        buf.putInt(msg.arg0);
        buf.putInt(msg.arg1);
        buf.putInt(payload.length);
        buf.putInt(crc);
        buf.putInt(msg.command ^ 0xffffffff);
        buf.put(payload);
        out.write(buf.array());
        out.flush();
    }

    public static AdbMessage read(InputStream in) throws IOException {
        byte[] header = readExactly(in, 24);
        ByteBuffer buf = ByteBuffer.wrap(header);
        buf.order(ByteOrder.LITTLE_ENDIAN);
        int command = buf.getInt();
        int arg0 = buf.getInt();
        int arg1 = buf.getInt();
        int len = buf.getInt();
        /* int crc = */ buf.getInt();
        /* int magic = */ buf.getInt();
        byte[] data = len > 0 ? readExactly(in, len) : new byte[0];
        return new AdbMessage(command, arg0, arg1, data);
    }

    public static byte[] readExactly(InputStream in, int n) throws IOException {
        byte[] b = new byte[n];
        int off = 0;
        while (off < n) {
            int r = in.read(b, off, n - off);
            if (r < 0) throw new IOException("ADB 连接意外断开 (EOF)");
            off += r;
        }
        return b;
    }

    public static int crc32(byte[] data) {
        CRC32 c = new CRC32();
        c.update(data);
        return (int) c.getValue();
    }
}
