Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit af4ba04d authored by Bram Bonné's avatar Bram Bonné Committed by Android (Google) Code Review
Browse files

Merge "Ports DiffScriptBackupWriter from gmscore to AOSP."

parents 1fc8cdea 02cf411d
Loading
Loading
Loading
Loading
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup.encryption.chunking;

import com.android.internal.util.Preconditions;

/** Representation of a range of bytes to be downloaded. */
final class ByteRange {
    private final long mStart;
    private final long mEnd;

    /** Creates a range of bytes which includes {@code mStart} and {@code mEnd}. */
    ByteRange(long start, long end) {
        Preconditions.checkArgument(start >= 0);
        Preconditions.checkArgument(end >= start);
        mStart = start;
        mEnd = end;
    }

    /** Returns the start of the {@code ByteRange}. The start is included in the range. */
    long getStart() {
        return mStart;
    }

    /** Returns the end of the {@code ByteRange}. The end is included in the range. */
    long getEnd() {
        return mEnd;
    }

    /** Returns the number of bytes included in the {@code ByteRange}. */
    int getLength() {
        return (int) (mEnd - mStart + 1);
    }

    /** Creates a new {@link ByteRange} from {@code mStart} to {@code mEnd + length}. */
    ByteRange extend(long length) {
        Preconditions.checkArgument(length > 0);
        return new ByteRange(mStart, mEnd + length);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof ByteRange)) {
            return false;
        }

        ByteRange byteRange = (ByteRange) o;
        return (mEnd == byteRange.mEnd && mStart == byteRange.mStart);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + (int) (mStart ^ (mStart >>> 32));
        result = 31 * result + (int) (mEnd ^ (mEnd >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return String.format("ByteRange{mStart=%d, mEnd=%d}", mStart, mEnd);
    }
}
+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup.encryption.chunking;

import com.android.internal.annotations.VisibleForTesting;

import java.io.IOException;
import java.io.OutputStream;

/** Writes backup data to a diff script, using a {@link SingleStreamDiffScriptWriter}. */
public class DiffScriptBackupWriter implements BackupWriter {
    /**
     * The maximum size of a chunk in the diff script. The diff script writer {@code mWriter} will
     * buffer this many bytes in memory.
     */
    private static final int ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES = 1024 * 1024;

    private final SingleStreamDiffScriptWriter mWriter;
    private long mBytesWritten;

    /**
     * Constructs a new writer which writes the diff script to the given output stream, using the
     * maximum new chunk size {@code ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES}.
     */
    public static DiffScriptBackupWriter newInstance(OutputStream outputStream) {
        SingleStreamDiffScriptWriter writer =
                new SingleStreamDiffScriptWriter(
                        outputStream, ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES);
        return new DiffScriptBackupWriter(writer);
    }

    @VisibleForTesting
    DiffScriptBackupWriter(SingleStreamDiffScriptWriter writer) {
        mWriter = writer;
    }

    @Override
    public void writeBytes(byte[] bytes) throws IOException {
        for (byte b : bytes) {
            mWriter.writeByte(b);
        }

        mBytesWritten += bytes.length;
    }

    @Override
    public void writeChunk(long start, int length) throws IOException {
        mWriter.writeChunk(start, length);
        mBytesWritten += length;
    }

    @Override
    public long getBytesWritten() {
        return mBytesWritten;
    }

    @Override
    public void flush() throws IOException {
        mWriter.flush();
    }
}
+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup.encryption.chunking;

import java.io.IOException;
import java.io.OutputStream;

/** Writer that formats a Diff Script and writes it to an output source. */
interface DiffScriptWriter {
    /** Adds a new byte to the diff script. */
    void writeByte(byte b) throws IOException;

    /** Adds a known chunk to the diff script. */
    void writeChunk(long chunkStart, int chunkLength) throws IOException;

    /** Indicates that no more bytes or chunks will be added to the diff script. */
    void flush() throws IOException;

    interface Factory {
        DiffScriptWriter create(OutputStream outputStream);
    }
}
+25 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup.encryption.chunking;

import java.io.OutputStream;

/** An interface that wraps one {@link OutputStream} with another for filtration purposes. */
public interface OutputStreamWrapper {
    /** Wraps a given {@link OutputStream}. */
    OutputStream wrap(OutputStream outputStream);
}
+130 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup.encryption.chunking;

import android.annotation.Nullable;

import com.android.internal.util.Preconditions;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Locale;

/**
 * A {@link DiffScriptWriter} that writes an entire diff script to a single {@link OutputStream}.
 */
public class SingleStreamDiffScriptWriter implements DiffScriptWriter {
    static final byte LINE_SEPARATOR = 0xA;
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private final int mMaxNewByteChunkSize;
    private final OutputStream mOutputStream;
    private final byte[] mByteBuffer;
    private int mBufferSize = 0;
    // Each chunk could be written immediately to the output stream. However,
    // it is possible that chunks may overlap. We therefore cache the most recent
    // reusable chunk and try to merge it with future chunks.
    private ByteRange mReusableChunk;

    public SingleStreamDiffScriptWriter(OutputStream outputStream, int maxNewByteChunkSize) {
        mOutputStream = outputStream;
        mMaxNewByteChunkSize = maxNewByteChunkSize;
        mByteBuffer = new byte[maxNewByteChunkSize];
    }

    @Override
    public void writeByte(byte b) throws IOException {
        if (mReusableChunk != null) {
            writeReusableChunk();
        }
        mByteBuffer[mBufferSize++] = b;
        if (mBufferSize == mMaxNewByteChunkSize) {
            writeByteBuffer();
        }
    }

    @Override
    public void writeChunk(long chunkStart, int chunkLength) throws IOException {
        Preconditions.checkArgument(chunkStart >= 0);
        Preconditions.checkArgument(chunkLength > 0);
        if (mBufferSize != 0) {
            writeByteBuffer();
        }

        if (mReusableChunk != null && mReusableChunk.getEnd() + 1 == chunkStart) {
            // The new chunk overlaps the old, so combine them into a single byte range.
            mReusableChunk = mReusableChunk.extend(chunkLength);
        } else {
            writeReusableChunk();
            mReusableChunk = new ByteRange(chunkStart, chunkStart + chunkLength - 1);
        }
    }

    @Override
    public void flush() throws IOException {
        Preconditions.checkState(!(mBufferSize != 0 && mReusableChunk != null));
        if (mBufferSize != 0) {
            writeByteBuffer();
        }
        if (mReusableChunk != null) {
            writeReusableChunk();
        }
        mOutputStream.flush();
    }

    private void writeByteBuffer() throws IOException {
        mOutputStream.write(Integer.toString(mBufferSize).getBytes(UTF_8));
        mOutputStream.write(LINE_SEPARATOR);
        mOutputStream.write(mByteBuffer, 0, mBufferSize);
        mOutputStream.write(LINE_SEPARATOR);
        mBufferSize = 0;
    }

    private void writeReusableChunk() throws IOException {
        if (mReusableChunk != null) {
            mOutputStream.write(
                    String.format(
                                    Locale.US,
                                    "%d-%d",
                                    mReusableChunk.getStart(),
                                    mReusableChunk.getEnd())
                            .getBytes(UTF_8));
            mOutputStream.write(LINE_SEPARATOR);
            mReusableChunk = null;
        }
    }

    /** A factory that creates {@link SingleStreamDiffScriptWriter}s. */
    public static class Factory implements DiffScriptWriter.Factory {
        private final int mMaxNewByteChunkSize;
        private final OutputStreamWrapper mOutputStreamWrapper;

        public Factory(int maxNewByteChunkSize, @Nullable OutputStreamWrapper outputStreamWrapper) {
            mMaxNewByteChunkSize = maxNewByteChunkSize;
            mOutputStreamWrapper = outputStreamWrapper;
        }

        @Override
        public SingleStreamDiffScriptWriter create(OutputStream outputStream) {
            if (mOutputStreamWrapper != null) {
                outputStream = mOutputStreamWrapper.wrap(outputStream);
            }
            return new SingleStreamDiffScriptWriter(outputStream, mMaxNewByteChunkSize);
        }
    }
}
Loading