1package com.googlecode.mp4parser.boxes.piff;
2
3import com.coremedia.iso.IsoFile;
4import com.coremedia.iso.IsoTypeReader;
5import com.coremedia.iso.IsoTypeWriter;
6import com.googlecode.mp4parser.util.Path;
7
8import java.io.FileInputStream;
9import java.io.FileNotFoundException;
10import java.io.IOException;
11import java.io.UnsupportedEncodingException;
12import java.nio.ByteBuffer;
13import java.util.ArrayList;
14import java.util.Collections;
15import java.util.List;
16
17/**
18 * Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.7. ASF GUIDs
19 * <p/>
20 * <p/>
21 * ASF_Protection_System_Identifier_Object
22 * 9A04F079-9840-4286-AB92E65BE0885F95
23 * <p/>
24 * ASF_Content_Protection_System_Microsoft_PlayReady
25 * F4637010-03C3-42CD-B932B48ADF3A6A54
26 * <p/>
27 * ASF_StreamType_PlayReady_Encrypted_Command_Media
28 * 8683973A-6639-463A-ABD764F1CE3EEAE0
29 * <p/>
30 * <p/>
31 * Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.5. Data Objects > 2.5.1. Payload Extension for AES in Counter Mode
32 * <p/>
33 * The sample Id is used as the IV in CTR mode. Block offset, starting at 0 and incremented by 1 after every 16 bytes, from the beginning of the sample is used as the Counter.
34 * <p/>
35 * The sample ID for each sample (media object) is stored as an ASF payload extension system with the ID of ASF_Payload_Extension_Encryption_SampleID = {6698B84E-0AFA-4330-AEB2-1C0A98D7A44D}. The payload extension can be stored as a fixed size extension of 8 bytes.
36 * <p/>
37 * The sample ID is always stored in big-endian byte order.
38 */
39public class PlayReadyHeader extends ProtectionSpecificHeader {
40    private long length;
41    private List<PlayReadyRecord> records;
42
43    public PlayReadyHeader() {
44
45    }
46
47    @Override
48    public void parse(ByteBuffer byteBuffer) {
49        /*
50   Length DWORD 32
51
52   PlayReady Record Count WORD 16
53
54   PlayReady Records See Text Varies
55
56        */
57
58        length = IsoTypeReader.readUInt32BE(byteBuffer);
59        int recordCount = IsoTypeReader.readUInt16BE(byteBuffer);
60
61        records = PlayReadyRecord.createFor(byteBuffer, recordCount);
62    }
63
64    @Override
65    public ByteBuffer getData() {
66
67        int size = 4 + 2;
68        for (PlayReadyRecord record : records) {
69            size += 2 + 2;
70            size += record.getValue().rewind().limit();
71        }
72        ByteBuffer byteBuffer = ByteBuffer.allocate(size);
73
74        IsoTypeWriter.writeUInt32BE(byteBuffer, size);
75        IsoTypeWriter.writeUInt16BE(byteBuffer, records.size());
76        for (PlayReadyRecord record : records) {
77            IsoTypeWriter.writeUInt16BE(byteBuffer, record.type);
78            IsoTypeWriter.writeUInt16BE(byteBuffer, record.getValue().limit());
79            ByteBuffer tmp4debug = record.getValue();
80            byteBuffer.put(tmp4debug);
81        }
82
83        return byteBuffer;
84    }
85
86    public void setRecords(List<PlayReadyRecord> records) {
87        this.records = records;
88    }
89
90    public List<PlayReadyRecord> getRecords() {
91        return Collections.unmodifiableList(records);
92    }
93
94    @Override
95    public String toString() {
96        final StringBuilder sb = new StringBuilder();
97        sb.append("PlayReadyHeader");
98        sb.append("{length=").append(length);
99        sb.append(", recordCount=").append(records.size());
100        sb.append(", records=").append(records);
101        sb.append('}');
102        return sb.toString();
103    }
104
105    public static abstract class PlayReadyRecord {
106        int type;
107
108
109        public PlayReadyRecord(int type) {
110            this.type = type;
111        }
112
113        public static List<PlayReadyRecord> createFor(ByteBuffer byteBuffer, int recordCount) {
114            List<PlayReadyRecord> records = new ArrayList<PlayReadyRecord>(recordCount);
115
116            for (int i = 0; i < recordCount; i++) {
117                PlayReadyRecord record;
118                int type = IsoTypeReader.readUInt16BE(byteBuffer);
119                int length = IsoTypeReader.readUInt16BE(byteBuffer);
120                switch (type) {
121                    case 0x1:
122                        record = new RMHeader();
123                        break;
124                    case 0x2:
125                        record = new DefaulPlayReadyRecord(0x02);
126                        break;
127                    case 0x3:
128                        record = new EmeddedLicenseStore();
129                        break;
130                    default:
131                        record = new DefaulPlayReadyRecord(type);
132                }
133                record.parse((ByteBuffer) byteBuffer.slice().limit(length));
134                byteBuffer.position(byteBuffer.position() + length);
135                records.add(record);
136            }
137
138            return records;
139        }
140
141        public abstract void parse(ByteBuffer bytes);
142
143        @Override
144        public String toString() {
145            final StringBuilder sb = new StringBuilder();
146            sb.append("PlayReadyRecord");
147            sb.append("{type=").append(type);
148            sb.append(", length=").append(getValue().limit());
149//            sb.append(", value=").append(Hex.encodeHex(getValue())).append('\'');
150            sb.append('}');
151            return sb.toString();
152        }
153
154        public abstract ByteBuffer getValue();
155
156        public static class RMHeader extends PlayReadyRecord {
157            String header;
158
159            public RMHeader() {
160                super(0x01);
161            }
162
163            @Override
164            public void parse(ByteBuffer bytes) {
165                try {
166                    byte[] str = new byte[bytes.slice().limit()];
167                    bytes.get(str);
168                    header = new String(str, "UTF-16LE");
169                } catch (UnsupportedEncodingException e) {
170                    throw new RuntimeException(e);
171                }
172            }
173
174            @Override
175            public ByteBuffer getValue() {
176                byte[] headerBytes;
177                try {
178                    headerBytes = header.getBytes("UTF-16LE");
179                } catch (UnsupportedEncodingException e) {
180                    throw new RuntimeException(e);
181                }
182                return ByteBuffer.wrap(headerBytes);
183            }
184
185            public void setHeader(String header) {
186                this.header = header;
187            }
188
189            public String getHeader() {
190                return header;
191            }
192
193            @Override
194            public String toString() {
195                final StringBuilder sb = new StringBuilder();
196                sb.append("RMHeader");
197                sb.append("{length=").append(getValue().limit());
198                sb.append(", header='").append(header).append('\'');
199                sb.append('}');
200                return sb.toString();
201            }
202        }
203
204        public static class EmeddedLicenseStore extends PlayReadyRecord {
205            ByteBuffer value;
206
207            public EmeddedLicenseStore() {
208                super(0x03);
209            }
210
211            @Override
212            public void parse(ByteBuffer bytes) {
213                this.value = bytes.duplicate();
214            }
215
216            @Override
217            public ByteBuffer getValue() {
218                return value;
219            }
220
221            @Override
222            public String toString() {
223                final StringBuilder sb = new StringBuilder();
224                sb.append("EmeddedLicenseStore");
225                sb.append("{length=").append(getValue().limit());
226                //sb.append(", value='").append(Hex.encodeHex(getValue())).append('\'');
227                sb.append('}');
228                return sb.toString();
229            }
230        }
231
232        public static class DefaulPlayReadyRecord extends PlayReadyRecord {
233            ByteBuffer value;
234
235            public DefaulPlayReadyRecord(int type) {
236                super(type);
237            }
238
239            @Override
240            public void parse(ByteBuffer bytes) {
241                this.value = bytes.duplicate();
242            }
243
244            @Override
245            public ByteBuffer getValue() {
246                return value;
247            }
248
249        }
250
251    }
252
253}
254