summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java
blob: ddb13aeb9c731c48ab7bce6f07014a200aea2b5b (plain)
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
/*
 * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.mp4;

import androidx.annotation.Nullable;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
import java.nio.ByteBuffer;
import java.util.UUID;

/**
 * Utility methods for handling PSSH atoms.
 */
public final class PsshAtomUtil {

  private static final String TAG = "PsshAtomUtil";

  private PsshAtomUtil() {}

  /**
   * Builds a version 0 PSSH atom for a given system id, containing the given data.
   *
   * @param systemId The system id of the scheme.
   * @param data The scheme specific data.
   * @return The PSSH atom.
   */
  public static byte[] buildPsshAtom(UUID systemId, @Nullable byte[] data) {
    return buildPsshAtom(systemId, null, data);
  }

  /**
   * Builds a PSSH atom for the given system id, containing the given key ids and data.
   *
   * @param systemId The system id of the scheme.
   * @param keyIds The key ids for a version 1 PSSH atom, or null for a version 0 PSSH atom.
   * @param data The scheme specific data.
   * @return The PSSH atom.
   */
  // dereference of possibly-null reference keyId
  @SuppressWarnings({"ParameterNotNullable", "nullness:dereference.of.nullable"})
  public static byte[] buildPsshAtom(
      UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) {
    int dataLength = data != null ? data.length : 0;
    int psshBoxLength = Atom.FULL_HEADER_SIZE + 16 /* SystemId */ + 4 /* DataSize */ + dataLength;
    if (keyIds != null) {
      psshBoxLength += 4 /* KID_count */ + (keyIds.length * 16) /* KIDs */;
    }
    ByteBuffer psshBox = ByteBuffer.allocate(psshBoxLength);
    psshBox.putInt(psshBoxLength);
    psshBox.putInt(Atom.TYPE_pssh);
    psshBox.putInt(keyIds != null ? 0x01000000 : 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */);
    psshBox.putLong(systemId.getMostSignificantBits());
    psshBox.putLong(systemId.getLeastSignificantBits());
    if (keyIds != null) {
      psshBox.putInt(keyIds.length);
      for (UUID keyId : keyIds) {
        psshBox.putLong(keyId.getMostSignificantBits());
        psshBox.putLong(keyId.getLeastSignificantBits());
      }
    }
    if (data != null && data.length != 0) {
      psshBox.putInt(data.length);
      psshBox.put(data);
    } // Else the last 4 bytes are a 0 DataSize.
    return psshBox.array();
  }

  /**
   * Returns whether the data is a valid PSSH atom.
   *
   * @param data The data to parse.
   * @return Whether the data is a valid PSSH atom.
   */
  public static boolean isPsshAtom(byte[] data) {
    return parsePsshAtom(data) != null;
  }

  /**
   * Parses the UUID from a PSSH atom. Version 0 and 1 PSSH atoms are supported.
   *
   * <p>The UUID is only parsed if the data is a valid PSSH atom.
   *
   * @param atom The atom to parse.
   * @return The parsed UUID. Null if the input is not a valid PSSH atom, or if the PSSH atom has an
   *     unsupported version.
   */
  public static @Nullable UUID parseUuid(byte[] atom) {
    PsshAtom parsedAtom = parsePsshAtom(atom);
    if (parsedAtom == null) {
      return null;
    }
    return parsedAtom.uuid;
  }

  /**
   * Parses the version from a PSSH atom. Version 0 and 1 PSSH atoms are supported.
   * <p>
   * The version is only parsed if the data is a valid PSSH atom.
   *
   * @param atom The atom to parse.
   * @return The parsed version. -1 if the input is not a valid PSSH atom, or if the PSSH atom has
   *     an unsupported version.
   */
  public static int parseVersion(byte[] atom) {
    PsshAtom parsedAtom = parsePsshAtom(atom);
    if (parsedAtom == null) {
      return -1;
    }
    return parsedAtom.version;
  }

  /**
   * Parses the scheme specific data from a PSSH atom. Version 0 and 1 PSSH atoms are supported.
   *
   * <p>The scheme specific data is only parsed if the data is a valid PSSH atom matching the given
   * UUID, or if the data is a valid PSSH atom of any type in the case that the passed UUID is null.
   *
   * @param atom The atom to parse.
   * @param uuid The required UUID of the PSSH atom, or null to accept any UUID.
   * @return The parsed scheme specific data. Null if the input is not a valid PSSH atom, or if the
   *     PSSH atom has an unsupported version, or if the PSSH atom does not match the passed UUID.
   */
  public static @Nullable byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) {
    PsshAtom parsedAtom = parsePsshAtom(atom);
    if (parsedAtom == null) {
      return null;
    }
    if (uuid != null && !uuid.equals(parsedAtom.uuid)) {
      Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.uuid + ".");
      return null;
    }
    return parsedAtom.schemeData;
  }

  /**
   * Parses a PSSH atom. Version 0 and 1 PSSH atoms are supported.
   *
   * @param atom The atom to parse.
   * @return The parsed PSSH atom. Null if the input is not a valid PSSH atom, or if the PSSH atom
   *     has an unsupported version.
   */
  // TODO: Support parsing of the key ids for version 1 PSSH atoms.
  private static @Nullable PsshAtom parsePsshAtom(byte[] atom) {
    ParsableByteArray atomData = new ParsableByteArray(atom);
    if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) {
      // Data too short.
      return null;
    }
    atomData.setPosition(0);
    int atomSize = atomData.readInt();
    if (atomSize != atomData.bytesLeft() + 4) {
      // Not an atom, or incorrect atom size.
      return null;
    }
    int atomType = atomData.readInt();
    if (atomType != Atom.TYPE_pssh) {
      // Not an atom, or incorrect atom type.
      return null;
    }
    int atomVersion = Atom.parseFullAtomVersion(atomData.readInt());
    if (atomVersion > 1) {
      Log.w(TAG, "Unsupported pssh version: " + atomVersion);
      return null;
    }
    UUID uuid = new UUID(atomData.readLong(), atomData.readLong());
    if (atomVersion == 1) {
      int keyIdCount = atomData.readUnsignedIntToInt();
      atomData.skipBytes(16 * keyIdCount);
    }
    int dataSize = atomData.readUnsignedIntToInt();
    if (dataSize != atomData.bytesLeft()) {
      // Incorrect dataSize.
      return null;
    }
    byte[] data = new byte[dataSize];
    atomData.readBytes(data, 0, dataSize);
    return new PsshAtom(uuid, atomVersion, data);
  }

  // TODO: Consider exposing this and making parsePsshAtom public.
  private static class PsshAtom {

    private final UUID uuid;
    private final int version;
    private final byte[] schemeData;

    public PsshAtom(UUID uuid, int version, byte[] schemeData) {
      this.uuid = uuid;
      this.version = version;
      this.schemeData = schemeData;
    }

  }

}