├── .gitignore ├── proto_compiler ├── bin │ └── protoc.exe └── include │ └── google │ └── protobuf │ ├── source_context.proto │ ├── empty.proto │ ├── struct.proto │ ├── wrappers.proto │ ├── duration.proto │ ├── any.proto │ ├── type.proto │ ├── timestamp.proto │ ├── api.proto │ ├── field_mask.proto │ ├── compiler │ └── plugin.proto │ └── descriptor.proto ├── requirements.txt ├── cdm ├── devices │ └── android_generic │ │ ├── token.bin │ │ ├── device_client_id_blob │ │ ├── config.json │ │ └── device_private_key ├── key.py ├── session.py ├── formats │ ├── 1.txt │ ├── 2.txt │ ├── sample_request_curl.txt │ ├── wv_proto3.proto │ └── wv_proto2.proto ├── deviceconfig.py ├── vmp.py └── cdm.py ├── .github └── ISSUE_TEMPLATE │ └── feature-request--add-site-support.md ├── README.md ├── getPSSH.py ├── wvdecryptcustom.py ├── headers.py └── l3.py /.gitignore: -------------------------------------------------------------------------------- 1 | cookies.py 2 | __pycache__ 3 | 4 | -------------------------------------------------------------------------------- /proto_compiler/bin/protoc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/widevine_keys/HEAD/proto_compiler/bin/protoc.exe -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | xmltodict 2 | requests 3 | google 4 | google-api-python-client 5 | protobuf 6 | pycryptodome 7 | pycryptodomex -------------------------------------------------------------------------------- /cdm/devices/android_generic/token.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/widevine_keys/HEAD/cdm/devices/android_generic/token.bin -------------------------------------------------------------------------------- /cdm/devices/android_generic/device_client_id_blob: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nilaoda/widevine_keys/HEAD/cdm/devices/android_generic/device_client_id_blob -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request--add-site-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Feature request: Add site support' 3 | about: If your site require specific json-formatted data in the license request 4 | title: Add [site_name] support 5 | labels: add site support 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What additional fields does your site require?** 11 | A clear and concise description or screenshot of payload tab of post-request in chrome devtools 12 | 13 | **Describe algorithm for generating additional fields if they are not static** 14 | [text] 15 | -------------------------------------------------------------------------------- /cdm/key.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | 3 | class Key: 4 | def __init__(self, kid, type, key, permissions=[]): 5 | self.kid = kid 6 | self.type = type 7 | self.key = key 8 | self.permissions = permissions 9 | 10 | def __repr__(self): 11 | if self.type == "OPERATOR_SESSION": 12 | return "key(kid={}, type={}, key={}, permissions={})".format(self.kid, self.type, binascii.hexlify(self.key), self.permissions) 13 | else: 14 | return "key(kid={}, type={}, key={})".format(self.kid, self.type, binascii.hexlify(self.key)) 15 | -------------------------------------------------------------------------------- /cdm/session.py: -------------------------------------------------------------------------------- 1 | class Session: 2 | def __init__(self, session_id, init_data, device_config, offline): 3 | self.session_id = session_id 4 | self.init_data = init_data 5 | self.offline = offline 6 | self.device_config = device_config 7 | self.device_key = None 8 | self.session_key = None 9 | self.derived_keys = { 10 | 'enc': None, 11 | 'auth_1': None, 12 | 'auth_2': None 13 | } 14 | self.license_request = None 15 | self.license = None 16 | self.service_certificate = None 17 | self.privacy_mode = False 18 | self.keys = [] 19 | -------------------------------------------------------------------------------- /cdm/devices/android_generic/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "token.bin", 3 | "client_info": 4 | { 5 | "company_name": "motorola", 6 | "model_name": "Nexus 6", 7 | "architecture_name": "armeabi-v7a", 8 | "device_name": "shamu", 9 | "product_name": "shamu", 10 | "build_info": "google/shamu/shamu:5.1.1/LMY48M/2167285:user/release-keys", 11 | "device_id": "TU1JX0VGRkYwRkU2NUQ5OAAAAAAAAAAAAAAAAAAAAAA=", 12 | "os_version": "5.1.1" 13 | }, 14 | "capabilities": 15 | { 16 | "session_token": 1, 17 | "max_hdcp_version": "HDCP_V2_2", 18 | "oem_crypto_api_version": 9 19 | } 20 | } -------------------------------------------------------------------------------- /cdm/devices/android_generic/device_private_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEA4sUKDpvMG/idF8oCH5AVSwFd5Mk+rEwOBsLZMYdliXWe1hn9 3 | mdE6u9pjsr+bLrZjlKxMFqPPxbIUcC1Ii7BFSje2Fd8kxnaIprQWxDPgK+NSSx7v 4 | Un452TyB1L9lx39ZBt0PlRfwjkCodX+I9y+oBga73NRh7hPbtLzXe/r/ubFBaEu+ 5 | aRkDZBwYPqHgH1RoFLuyFNMjfqGcPosGxceDtvPysmBxB93Hk2evml5fjdYGg6tx 6 | z510g+XFPDFv7GSy1KuWqit83MqzPls9qAQMkwUc05ggjDhGCKW4/p97fn23WDFE 7 | 3TzSSsQvyJLKA3s9oJbtJCD/gOHYqDvnWn8zPwIDAQABAoIBAQDCWe1Mp+o+7sx0 8 | XwWC15HoPruiIXg9YtGCqexLrqcvMEd5Z70Z32BfL8TSpbTyTA78lM6BeNPRs9Yg 9 | bi8GyYQZH7ZG+IAkN+LWPPJmJa+y7ZjSGSkzoksiC+GZ3I/2cwZyA3Qfa+0XfgLi 10 | 8PMKJyXyREMt+DgWO57JQC/OakhRdCR19mM6NKd+ynd/IEz/NIbjMLDVKwW8HEPx 11 | N3r5CU9O96nr62DI68KVj3jwUR3cDi/5xfhosYhCQjHJuobNbeFR18dY2nQNLWYd 12 | S0wtskla1fl9eYHwYAzwru4wHT4WJC7+V4pscfCI0YZB6PslxDKrv73l5H1tz4cf 13 | Vy58NRSBAoGBAPSmjoVtQzTvQ6PZIs81SF1ulJI9kUpyFaBoSSgt+2ZkeNtF6Hih 14 | Zm7OVJ9wg9sfjpB3SFBUjuhXz/ts/t6dkA2PgCbrvhBMRKSGbfyhhtM2gRf002I4 15 | bJ7Y0C/ont4WzC/XbXEkAmh+fG2/JRvbdVQaIdyS6MmVHtCtRsHEQZS5AoGBAO1K 16 | IXOKAFA+320+Hkbqskfevmxrv+JHIdetliaREZwQH+VYUUM8u5/Kt3oyMat+mH90 17 | rZOKQK2zM8cz4tKclTUT54nrtICxeo6UHVc56FqXZ6sVvVgm8Cnvt1md4XwG4FwQ 18 | r/OlaM6Hr5HRf8dkzuzqm4ZQYRHGzZ6AMphj8Xu3AoGAdmo7p5dIJVH98kuCDrsi 19 | iJ6iaNpF/buUfiyb5EfFXD0bRj7jE6hDdTSHPxjtqVzv2zrxFHipJwqBz5dlEYlA 20 | FWA0ziHiv+66dsveZp4kLQ0/lMHaorre0E/vDJFSe/qa4DksbsvYIo2+WjxfkMk7 21 | U/bGFwZAiHmWDbkg+16rw3kCgYEAyyodWf9eJVavlakJ404vNrnP8KSQtfyRTUii 22 | toKewTBNHuBvM1JckoPOdCFlxZ+ukfIka56DojU8r+IM4qaOWdOg+sWE1mses9S9 23 | CmHaPzZC3IjQhRlRp5ZHNcOnu7lnf2wKOmH1Sl+CQydMcDwvr0lvv6AyfDXq9zps 24 | F2365CECgYEAmYgs/qwnh9m0aGDw/ZGrASoE0TxlpizPvsVDGx9t9UGC2Z+5QvAE 25 | ZcQeKoLCbktr0BnRLI+W1g+KpXQGcnSF9VX/qwUlf72XA6C6kobQvW+Yd/H/IN5d 26 | jPqoL/m41rRzm+J+9/Tfc8Aiy1kkllUYnVJdC5QLAIswuhI8lkaFTN4= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

First run:

2 | 3 | [Copy headers ](https://user-images.githubusercontent.com/43696206/145660715-472e4c65-86de-453f-86fc-5bb14028f448.png)(with cookies) of POST license request from browser to headers.py like dictionary.
4 | 5 | ``` 6 | pip install -r requirements.txt # if doesn't work try pip3 7 | py l3.py 8 | Input MPD URL: https://site.ru/.../.../filename.mpd 9 | License URL: https://cms.35mm.online/umbraco/api/products/473/drm/widevine?platform=BROWSER&type=MOVIE 10 | ``` 11 | 12 | Works only if the site does not require specific json-formatted data in the license request. 13 | And if it requires and you want to add its support, write to issues specifying the required json-formatted fields and the algorithm for their formation (if not static). 14 |
Examples:
15 | 1. Normal work:
16 | ![Normal work](https://user-images.githubusercontent.com/43696206/145641480-bf3a07a6-2d6e-4dee-9398-b4ecdf8bf273.png)
17 | 2. Server did not issue a license, as it requires additional json-formatted data: 18 | ![error_teapot](https://user-images.githubusercontent.com/43696206/145643061-8e44b226-a3c2-4c44-8c62-6db84e582d9e.png)
19 | 3. If "Unable to find PSSH in mpd" - use [this tool](https://tools.axinom.com/generators/PsshBox) to get it manually or write to issues attaching a link to mpd
20 | 21 | Further about kinopoisk (hello to compatriots):
22 | Not working yet. 23 | It is necessary to find out how the POST request is signed (the signature field in the request payload). 24 | It looks like a simple hash of sha1, but from the looks of it, this is not it, but the Amazon's AWS Signature Version 4. Or I am doing something wrong and everything is much easier... 25 | Any ideas are appreciated, write to issues. 26 | 27 | [Parsed payload of license request](https://user-images.githubusercontent.com/43696206/145263764-349dd8be-58ec-4d42-9524-4a098b0fe5e3.png) 28 | 29 | -------------------------------------------------------------------------------- /getPSSH.py: -------------------------------------------------------------------------------- 1 | import requests, xmltodict, json 2 | 3 | def get_pssh(mpd_url): 4 | pssh = '' 5 | try: 6 | r = requests.get(url=mpd_url) 7 | r.raise_for_status() 8 | xml = xmltodict.parse(r.text) 9 | mpd = json.loads(json.dumps(xml)) 10 | periods = mpd['MPD']['Period'] 11 | except Exception as e: 12 | pssh = input(f'\nUnable to find PSSH in MPD: {e}. \nEdit getPSSH.py or enter PSSH manually: ') 13 | return pssh 14 | if isinstance(periods, list): 15 | for idx, period in enumerate(periods): 16 | if isinstance(period['AdaptationSet'], list): 17 | for ad_set in period['AdaptationSet']: 18 | if ad_set['@mimeType'] == 'video/mp4': 19 | try: 20 | for t in ad_set['ContentProtection']: 21 | if t['@schemeIdUri'].lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed": 22 | pssh = t["cenc:pssh"] 23 | except KeyError: 24 | pass 25 | else: 26 | if period['AdaptationSet']['@mimeType'] == 'video/mp4': 27 | try: 28 | for t in period['AdaptationSet']['ContentProtection']: 29 | if t['@schemeIdUri'].lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed": 30 | pssh = t["cenc:pssh"] 31 | except KeyError: 32 | pass 33 | else: 34 | for ad_set in periods['AdaptationSet']: 35 | if ad_set['@mimeType'] == 'video/mp4': 36 | try: 37 | for t in ad_set['ContentProtection']: 38 | if t['@schemeIdUri'].lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed": 39 | pssh = t["cenc:pssh"] 40 | except KeyError: 41 | pass 42 | if pssh == '': 43 | pssh = input('Unable to find PSSH in mpd. Edit getPSSH.py or enter PSSH manually: ') 44 | return pssh -------------------------------------------------------------------------------- /wvdecryptcustom.py: -------------------------------------------------------------------------------- 1 | # uncompyle6 version 3.7.3 2 | # Python bytecode 3.6 (3379) 3 | # Decompiled from: Python 3.7.8 (tags/v3.7.8:4b47a5b6ba, Jun 28 2020, 08:53:46) [MSC v.1916 64 bit (AMD64)] 4 | # Embedded file name: pywidevine\decrypt\wvdecryptcustom.py 5 | import logging, subprocess, re, base64 6 | from cdm import cdm, deviceconfig 7 | 8 | class WvDecrypt(object): 9 | WV_SYSTEM_ID = [ 10 | 237, 239, 139, 169, 121, 214, 74, 206, 163, 200, 39, 220, 213, 29, 33, 237] 11 | 12 | def __init__(self, init_data_b64, cert_data_b64, device): 13 | self.init_data_b64 = init_data_b64 14 | self.cert_data_b64 = cert_data_b64 15 | self.device = device 16 | self.cdm = cdm.Cdm() 17 | 18 | def check_pssh(pssh_b64): 19 | pssh = base64.b64decode(pssh_b64) 20 | if not pssh[12:28] == bytes(self.WV_SYSTEM_ID): 21 | new_pssh = bytearray([0, 0, 0]) 22 | new_pssh.append(32 + len(pssh)) 23 | new_pssh[4:] = bytearray(b'pssh') 24 | new_pssh[8:] = [0, 0, 0, 0] 25 | new_pssh[13:] = self.WV_SYSTEM_ID 26 | new_pssh[29:] = [0, 0, 0, 0] 27 | new_pssh[31] = len(pssh) 28 | new_pssh[32:] = pssh 29 | return base64.b64encode(new_pssh) 30 | else: 31 | return pssh_b64 32 | 33 | self.session = self.cdm.open_session(check_pssh(self.init_data_b64), deviceconfig.DeviceConfig(self.device)) 34 | if self.cert_data_b64: 35 | self.cdm.set_service_certificate(self.session, self.cert_data_b64) 36 | 37 | def log_message(self, msg): 38 | return '{}'.format(msg) 39 | 40 | def start_process(self): 41 | keyswvdecrypt = [] 42 | try: 43 | for key in self.cdm.get_keys(self.session): 44 | if key.type == 'CONTENT': 45 | keyswvdecrypt.append(self.log_message('{}:{}'.format(key.kid.hex(), key.key.hex()))) 46 | 47 | except Exception: 48 | return ( 49 | False, keyswvdecrypt) 50 | else: 51 | return ( 52 | True, keyswvdecrypt) 53 | 54 | def get_challenge(self): 55 | return self.cdm.get_license_request(self.session) 56 | 57 | def update_license(self, license_b64): 58 | self.cdm.provide_license(self.session, license_b64) 59 | return True -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/source_context.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "SourceContextProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; 41 | 42 | // `SourceContext` represents information about the source of a 43 | // protobuf element, like the file in which it is defined. 44 | message SourceContext { 45 | // The path-qualified name of the .proto file that contained the associated 46 | // protobuf element. For example: `"google/protobuf/source_context.proto"`. 47 | string file_name = 1; 48 | } 49 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/empty.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/emptypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "EmptyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | option cc_enable_arenas = true; 42 | 43 | // A generic empty message that you can re-use to avoid defining duplicated 44 | // empty messages in your APIs. A typical example is to use it as the request 45 | // or the response type of an API method. For instance: 46 | // 47 | // service Foo { 48 | // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); 49 | // } 50 | // 51 | // The JSON representation for `Empty` is empty JSON object `{}`. 52 | message Empty {} 53 | -------------------------------------------------------------------------------- /cdm/formats/1.txt: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /cdm/deviceconfig.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | device_android_generic = { 4 | 'name': 'android_generic', 5 | 'description': 'android studio cdm', 6 | 'security_level': 3, 7 | 'session_id_type': 'android', 8 | 'private_key_available': True, 9 | 'vmp': False, 10 | 'send_key_control_nonce': True 11 | } 12 | 13 | devices_available = [device_android_generic] 14 | 15 | FILES_FOLDER = 'devices' 16 | 17 | class DeviceConfig: 18 | def __init__(self, device): 19 | self.device_name = device['name'] 20 | self.description = device['description'] 21 | self.security_level = device['security_level'] 22 | self.session_id_type = device['session_id_type'] 23 | self.private_key_available = device['private_key_available'] 24 | self.vmp = device['vmp'] 25 | self.send_key_control_nonce = device['send_key_control_nonce'] 26 | 27 | if 'keybox_filename' in device: 28 | self.keybox_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], device['keybox_filename']) 29 | else: 30 | self.keybox_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], 'keybox') 31 | 32 | if 'device_cert_filename' in device: 33 | self.device_cert_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], device['device_cert_filename']) 34 | else: 35 | self.device_cert_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], 'device_cert') 36 | 37 | if 'device_private_key_filename' in device: 38 | self.device_private_key_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], device['device_private_key_filename']) 39 | else: 40 | self.device_private_key_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], 'device_private_key') 41 | 42 | if 'device_client_id_blob_filename' in device: 43 | self.device_client_id_blob_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], device['device_client_id_blob_filename']) 44 | else: 45 | self.device_client_id_blob_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], 'device_client_id_blob') 46 | 47 | if 'device_vmp_blob_filename' in device: 48 | self.device_vmp_blob_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], device['device_vmp_blob_filename']) 49 | else: 50 | self.device_vmp_blob_filename = os.path.join(os.path.dirname(__file__), FILES_FOLDER, device['name'], 'device_vmp_blob') 51 | 52 | def __repr__(self): 53 | return "DeviceConfig(name={}, description={}, security_level={}, session_id_type={}, private_key_available={}, vmp={})".format(self.device_name, self.description, self.security_level, self.session_id_type, self.private_key_available, self.vmp) 54 | -------------------------------------------------------------------------------- /cdm/formats/2.txt: -------------------------------------------------------------------------------- 1 | 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 2 | 3 | # D:\Projects\hd.kinopoisk.ru\kinopoisk_keys_obtaining\proto_compiler\bin>protoc.exe -I=D:\Projects\hd.kinopoisk.ru\kinopoisk_keys_obtaining --python_out=D:\Projects\hd.kinopoisk.ru\kinopoisk_keys_obtaining "D:\Projects\hd.kinopoisk.ru\kinopoisk_keys_obtaining\cdm\formats\wv_proto2.proto" -------------------------------------------------------------------------------- /headers.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | headers = { 4 | 'authority': 'drmtoday.vieon.vn', 5 | 'sec-ch-ua': '"Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"', 6 | 'sec-ch-ua-mobile': '?0', 7 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36', 8 | 'x-dt-custom-data': 'eyJ1c2VySWQiOiIxMi1hZmRjM2Y5Zjc0OTM4YWE4M2JlMmEzMGE4YzA2MGY3NyIsInNlc3Npb25JZCI6ImV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUp2Y0dWeVlYUnZjbDlwWkNJNk1USXNJbk5sYzNOcGIyNUpaQ0k2SW1GbVpHTXpaamxtTFRjME9UTXRPR0ZoT0MwelltVXlMV0V6TUdFNFl6QTJNR1kzTnpFMk16a3pNakV3TURraUxDSjBhVzFsYzNSaGJYQWlPakUyTXprek1qRXdNRGtzSW5WelpYSkpaQ0k2SWpFeUxXRm1aR016WmpsbU56UTVNemhoWVRnelltVXlZVE13WVRoak1EWXdaamMzSW4wLmk5dzdYZlZZYWVwTmQyU2t4YWZhRVZRSXJSOXcxbHBxRXJ5WEFldTJjaU0iLCJtZXJjaGFudCI6InFuZXQifQ==', 9 | 'sec-ch-ua-platform': '"Windows"', 10 | 'accept': '/', 11 | 'origin': 'https://vieon.vn', 12 | 'sec-fetch-site': 'same-site', 13 | 'sec-fetch-mode': 'cors', 14 | 'sec-fetch-dest': 'empty', 15 | 'referer': 'https://vieon.vn/', 16 | 'accept-language': 'en-US,en;q=0.9,vi;q=0.8', 17 | } 18 | params = ( 19 | ('platform', 'BROWSER'), 20 | ('type', 'MOVIE'), 21 | ) 22 | # params inserted below will be passed to data-raw 23 | 24 | token = 'PFYtFSqWMYIROJYywBp0rbKvpD3z6N6E6UezNrZmCeKHkcJ5BiH4J8qwO0kfWOmrSsyFJMg9OqKKEwvuQiXL4qdDTa6ZYplNV9IQt6eEuveglDxjVlYd0vAecS5TZKn5mIs01YxcYNlTC7tu3BIk63JhMykMWEMmj1oEqaLa7N3ZrfU2VTlEPaWXBQV6PxnFeD022Yi298GOX/lDik7sXt5u4duT0aKIYbz0HPmjoh8b9Uy8bvzJUa0/SlgRcsW8RNTNjRJHM6N7BESCBGFEZcWoSXeDLEblEfRoBKro2iDGB1neLfRdY1fj7tJX1Rn9lj+OJwRTrBV4XoTQFuSsFwvYCeQfkAoRHXnze9njI5pY7sYgIGxuM4Zh3Wqaps6kxdO53QMm0jJ/L41X6r5CikSGKQ3Rcps8eQccZb9svEuyXMof2nm+eYL+AVPPrfmAf/BrmUHfiuF9CmNsF9pI7PjJfLeASmh3KDI/5gUuC4uJB7rjTzHVWNRz8MSvYjJ37/5V9+FTihpu04+0jFZ1AlGRks2dzk2IGvm5WcwUhsW2l/oQXlh7AgsksIl2DT0wH11WeG+h1/ijymMcG9AZzsxtkC53eLJ8bl55/Aag0TJfX4N6KvV7UhglXxkLifb06bLK/wlcGCfb01b4cnngcjPJEMxh2+Q7sJSnbyrtsfodzNah8JXITmx5kTkFFZkdtga1ha9jAb+Z40VMQfbrCLgh0GEe2G3VczcIt8QHAWAeS91TkzLbMRAugbgLvdbI8yQXtM15uo+TXrV4PNFprug33AnvnQu7IpNxlmgPMZhEn4Qqc/V++OFIpz/JYEMnLFvjYcXE2+sPc4uKnoE0xk33+IgZKtWtDhxRg2SJKyKOYxXucXybUuqPP/d+Nq1VNhD84Of+EfVvBjs1u75pNz/kmu5ms9ppRMkafq0/2311hbYi6VXmp6AeJweGJWkGKEFxw7xADuC8iM7xWnXW5aiNvhbsXBOwuTK8b2ioxjrg6OFhiMck+z9M5FHg+ktY7YB8iY+0V6B/Op2cQGtLaZ2E9lsomlQpxL+LiPI7UWEqmALEGH/Pk5tdLnlFRjEk2PPc8YURw+/nkP9tqJ/AXQwaXc9iwC5yxlYPCsp/Tc4=' 25 | provider = 'kakaotv' 26 | 27 | 28 | releasePid = "_qVpiY31v_oU" 29 | # response = requests.post('https://widevine-proxy.ott.yandex.ru/proxy', headers=headers, cookies=cookies, data=data) 30 | 31 | # print(f'{chr(10)}widevine_license: {response.content}') 32 | #NB. Original query string below. It seems impossible to parse and 33 | #reproduce query strings 100% accurately so the one below is given 34 | #in case the reproduced version is not "correct". 35 | # response = requests.options('https://api.ott.kinopoisk.ru/v12/license-affected-content-metadata?contentIds=4b63db58ab27e92b90a457e533b00007&serviceId=25', headers=headers) 36 | # dash-cenc/hdr10_uhd_hevc_ec3.mpd 37 | # https://strm.yandex.ru/vh-ottenc-converted/vod-content/4315082489d87677b21f7c83593fcb73/8614535x1631802676x41611665-4e76-41ac-93a7-5070b77b5f3c/dash-cenc/sdr_uhd_hevc_ec3.mpd 38 | 39 | # 'GET /certificate HTTP/1.1' 40 | -------------------------------------------------------------------------------- /cdm/vmp.py: -------------------------------------------------------------------------------- 1 | try: 2 | from google.protobuf.internal.decoder import _DecodeVarint as _di # this was tested to work with protobuf 3, but it's an internal API (any varint decoder might work) 3 | except ImportError: 4 | # this is generic and does not depend on pb internals, however it will decode "larger" possible numbers than pb decoder which has them fixed 5 | def LEB128_decode(buffer, pos, limit = 64): 6 | result = 0 7 | shift = 0 8 | while True: 9 | b = buffer[pos] 10 | pos += 1 11 | result |= ((b & 0x7F) << shift) 12 | if not (b & 0x80): 13 | return (result, pos) 14 | shift += 7 15 | if shift > limit: 16 | raise Exception("integer too large, shift: {}".format(shift)) 17 | _di = LEB128_decode 18 | 19 | 20 | class FromFileMixin: 21 | @classmethod 22 | def from_file(cls, filename): 23 | """Load given a filename""" 24 | with open(filename,"rb") as f: 25 | return cls(f.read()) 26 | 27 | # the signatures use a format internally similar to 28 | # protobuf's encoding, but without wire types 29 | class VariableReader(FromFileMixin): 30 | """Protobuf-like encoding reader""" 31 | 32 | def __init__(self, buf): 33 | self.buf = buf 34 | self.pos = 0 35 | self.size = len(buf) 36 | 37 | def read_int(self): 38 | """Read a variable length integer""" 39 | # _DecodeVarint will take care of out of range errors 40 | (val, nextpos) = _di(self.buf, self.pos) 41 | self.pos = nextpos 42 | return val 43 | 44 | def read_bytes_raw(self, size): 45 | """Read size bytes""" 46 | b = self.buf[self.pos:self.pos+size] 47 | self.pos += size 48 | return b 49 | 50 | def read_bytes(self): 51 | """Read a bytes object""" 52 | size = self.read_int() 53 | return self.read_bytes_raw(size) 54 | 55 | def is_end(self): 56 | return (self.size == self.pos) 57 | 58 | 59 | class TaggedReader(VariableReader): 60 | """Tagged reader, needed for implementing a WideVine signature reader""" 61 | 62 | def read_tag(self): 63 | """Read a tagged buffer""" 64 | return (self.read_int(), self.read_bytes()) 65 | 66 | def read_all_tags(self, max_tag=3): 67 | tags = {} 68 | while (not self.is_end()): 69 | (tag, bytes) = self.read_tag() 70 | if (tag > max_tag): 71 | raise IndexError("tag out of bound: got {}, max {}".format(tag, max_tag)) 72 | 73 | tags[tag] = bytes 74 | return tags 75 | 76 | class WideVineSignatureReader(FromFileMixin): 77 | """Parses a widevine .sig signature file.""" 78 | 79 | SIGNER_TAG = 1 80 | SIGNATURE_TAG = 2 81 | ISMAINEXE_TAG = 3 82 | 83 | def __init__(self, buf): 84 | reader = TaggedReader(buf) 85 | self.version = reader.read_int() 86 | if (self.version != 0): 87 | raise Exception("Unsupported signature format version {}".format(self.version)) 88 | self.tags = reader.read_all_tags() 89 | 90 | self.signer = self.tags[self.SIGNER_TAG] 91 | self.signature = self.tags[self.SIGNATURE_TAG] 92 | 93 | extra = self.tags[self.ISMAINEXE_TAG] 94 | if (len(extra) != 1 or (extra[0] > 1)): 95 | raise Exception("Unexpected 'ismainexe' field value (not '\\x00' or '\\x01'), please check: {0}".format(extra)) 96 | 97 | self.mainexe = bool(extra[0]) 98 | 99 | @classmethod 100 | def get_tags(cls, filename): 101 | """Return a dictionary of each tag in the signature file""" 102 | return cls.from_file(filename).tags 103 | -------------------------------------------------------------------------------- /cdm/formats/sample_request_curl.txt: -------------------------------------------------------------------------------- 1 | curl 'https://widevine-proxy.ott.yandex.ru/proxy' \ 2 | -H 'Connection: keep-alive' \ 3 | -H 'Pragma: no-cache' \ 4 | -H 'Cache-Control: no-cache' \ 5 | -H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"' \ 6 | -H 'DNT: 1' \ 7 | -H 'sec-ch-ua-mobile: ?0' \ 8 | -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36' \ 9 | -H 'sec-ch-ua-platform: "Windows"' \ 10 | -H 'content-type: application/json' \ 11 | -H 'Accept: */*' \ 12 | -H 'Origin: https://hd.kinopoisk.ru' \ 13 | -H 'Sec-Fetch-Site: cross-site' \ 14 | -H 'Sec-Fetch-Mode: cors' \ 15 | -H 'Sec-Fetch-Dest: empty' \ 16 | -H 'Referer: https://hd.kinopoisk.ru/' \ 17 | -H 'Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,id;q=0.6,de;q=0.5,zh-TW;q=0.4,zh-CN;q=0.3,zh;q=0.2,uk;q=0.1' \ 18 | -H 'Cookies were here...' \ 19 | --data-raw '{"rawLicenseRequestBase64":"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","contentId":"4315082489d87677b21f7c83593fcb73","contentTypeId":21,"serviceName":"ott-kp","productId":2,"monetizationModel":"SVOD","expirationTimestamp":1638667827,"verificationRequired":true,"signature":"83e0e97ddfe852b73a1093a68ec43a349ce05c87","version":"V4"}' \ 20 | --compressed --output - -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/struct.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/structpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "StructProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // `Struct` represents a structured data value, consisting of fields 44 | // which map to dynamically typed values. In some languages, `Struct` 45 | // might be supported by a native representation. For example, in 46 | // scripting languages like JS a struct is represented as an 47 | // object. The details of that representation are described together 48 | // with the proto support for the language. 49 | // 50 | // The JSON representation for `Struct` is JSON object. 51 | message Struct { 52 | // Unordered map of dynamically typed values. 53 | map fields = 1; 54 | } 55 | 56 | // `Value` represents a dynamically typed value which can be either 57 | // null, a number, a string, a boolean, a recursive struct value, or a 58 | // list of values. A producer of value is expected to set one of these 59 | // variants. Absence of any variant indicates an error. 60 | // 61 | // The JSON representation for `Value` is JSON value. 62 | message Value { 63 | // The kind of value. 64 | oneof kind { 65 | // Represents a null value. 66 | NullValue null_value = 1; 67 | // Represents a double value. 68 | double number_value = 2; 69 | // Represents a string value. 70 | string string_value = 3; 71 | // Represents a boolean value. 72 | bool bool_value = 4; 73 | // Represents a structured value. 74 | Struct struct_value = 5; 75 | // Represents a repeated `Value`. 76 | ListValue list_value = 6; 77 | } 78 | } 79 | 80 | // `NullValue` is a singleton enumeration to represent the null value for the 81 | // `Value` type union. 82 | // 83 | // The JSON representation for `NullValue` is JSON `null`. 84 | enum NullValue { 85 | // Null value. 86 | NULL_VALUE = 0; 87 | } 88 | 89 | // `ListValue` is a wrapper around a repeated field of values. 90 | // 91 | // The JSON representation for `ListValue` is JSON array. 92 | message ListValue { 93 | // Repeated field of dynamically typed values. 94 | repeated Value values = 1; 95 | } 96 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/wrappers.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Wrappers for primitive (non-message) types. These types are useful 32 | // for embedding primitives in the `google.protobuf.Any` type and for places 33 | // where we need to distinguish between the absence of a primitive 34 | // typed field and its default value. 35 | // 36 | // These wrappers have no meaningful use within repeated fields as they lack 37 | // the ability to detect presence on individual elements. 38 | // These wrappers have no meaningful use within a map or a oneof since 39 | // individual entries of a map or fields of a oneof can already detect presence. 40 | 41 | syntax = "proto3"; 42 | 43 | package google.protobuf; 44 | 45 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 46 | option cc_enable_arenas = true; 47 | option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; 48 | option java_package = "com.google.protobuf"; 49 | option java_outer_classname = "WrappersProto"; 50 | option java_multiple_files = true; 51 | option objc_class_prefix = "GPB"; 52 | 53 | // Wrapper message for `double`. 54 | // 55 | // The JSON representation for `DoubleValue` is JSON number. 56 | message DoubleValue { 57 | // The double value. 58 | double value = 1; 59 | } 60 | 61 | // Wrapper message for `float`. 62 | // 63 | // The JSON representation for `FloatValue` is JSON number. 64 | message FloatValue { 65 | // The float value. 66 | float value = 1; 67 | } 68 | 69 | // Wrapper message for `int64`. 70 | // 71 | // The JSON representation for `Int64Value` is JSON string. 72 | message Int64Value { 73 | // The int64 value. 74 | int64 value = 1; 75 | } 76 | 77 | // Wrapper message for `uint64`. 78 | // 79 | // The JSON representation for `UInt64Value` is JSON string. 80 | message UInt64Value { 81 | // The uint64 value. 82 | uint64 value = 1; 83 | } 84 | 85 | // Wrapper message for `int32`. 86 | // 87 | // The JSON representation for `Int32Value` is JSON number. 88 | message Int32Value { 89 | // The int32 value. 90 | int32 value = 1; 91 | } 92 | 93 | // Wrapper message for `uint32`. 94 | // 95 | // The JSON representation for `UInt32Value` is JSON number. 96 | message UInt32Value { 97 | // The uint32 value. 98 | uint32 value = 1; 99 | } 100 | 101 | // Wrapper message for `bool`. 102 | // 103 | // The JSON representation for `BoolValue` is JSON `true` and `false`. 104 | message BoolValue { 105 | // The bool value. 106 | bool value = 1; 107 | } 108 | 109 | // Wrapper message for `string`. 110 | // 111 | // The JSON representation for `StringValue` is JSON string. 112 | message StringValue { 113 | // The string value. 114 | string value = 1; 115 | } 116 | 117 | // Wrapper message for `bytes`. 118 | // 119 | // The JSON representation for `BytesValue` is JSON string. 120 | message BytesValue { 121 | // The bytes value. 122 | bytes value = 1; 123 | } 124 | -------------------------------------------------------------------------------- /l3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Module: widevine_keys 3 | # Created on: 10.12.2021 4 | # Authors: medvm 5 | # Version: 2.1.0 6 | 7 | import base64, requests, sys, xmltodict 8 | import headers 9 | # import cookies 10 | import json 11 | from cdm import cdm, deviceconfig 12 | from base64 import b64encode 13 | from getPSSH import get_pssh 14 | from wvdecryptcustom import WvDecrypt 15 | from cdm.formats import wv_proto2_pb2 as wv_proto2 16 | from urllib.parse import urlparse 17 | import logging 18 | # logging.basicConfig(level=logging.DEBUG) 19 | MDP_URL = input('\nInput MPD URL: ') 20 | lic_url = input('License URL: ') 21 | # hardcoded for kinopoisk.ru 22 | # lic_url = 'https://widevine-proxy.ott.yandex.ru/proxy' 23 | responses = [] 24 | license_b64 = '' 25 | pssh = get_pssh(MDP_URL) 26 | params = None 27 | params = urlparse(lic_url).query 28 | # pssh = 'AAAAXHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADwIARIQ7iYSc3cNGm7XKPe3hSn3MhoIdXNwLWNlbmMiGDdpWVNjM2NOR203WEtQZTNoU24zTWc9PSoAMgA=' 29 | # params from mdp_url: 30 | # ottsession=5945048d6f844d1699054cc5d44548f1& 31 | # puid=339572866& 32 | # video_content_id=4315082489d87677b21f7c83593fcb73& 33 | 34 | print(f'{chr(10)}PSSH obtained.\n{pssh}') 35 | 36 | def WV_Function(pssh, lic_url, cert_b64=None): 37 | """main func, emulates license request and then decrypt obtained license 38 | fileds that changes every new request is signature, expirationTimestamp, watchSessionId, puid, and rawLicenseRequestBase64 """ 39 | wvdecrypt = WvDecrypt(init_data_b64=pssh, cert_data_b64=cert_b64, device=deviceconfig.device_android_generic) 40 | raw_request = wvdecrypt.get_challenge() 41 | request = b64encode(raw_request) 42 | signature = cdm.hash_object 43 | # basic, mostly sites works 44 | responses.append(requests.post(url=lic_url, headers=headers.headers, data=raw_request, params=params)) 45 | # some another sites support 46 | responses.append(requests.post(url=lic_url, headers=headers.headers, params=params, 47 | json={ 48 | "rawLicenseRequestBase64": str(request, "utf-8" ), 49 | })) 50 | # kakaotv support 51 | responses.append(requests.post(url=lic_url, headers=headers.headers, params=params, 52 | data=f'token={headers.token}&provider={headers.provider}&payload={str(request, "utf-8" )}' 53 | )) 54 | # xfinity.com support 55 | headers.headers['licenseRequest'] = str(request, "utf-8" ) 56 | responses.append(requests.post(url=lic_url, headers=headers.headers, params=params, 57 | )) 58 | del headers.headers['licenseRequest'] 59 | # rte.ie support 60 | responses.append(requests.post(url=lic_url, headers=headers.headers, params=params, 61 | json={ 62 | "getWidevineLicense": 63 | { 64 | 'releasePid': headers.releasePid, 65 | 'widevineChallenge': str(request, "utf-8" ) 66 | }, 67 | })) 68 | # kinopoisk support 69 | responses.append(requests.post(url=lic_url, headers=headers.headers, params=params, 70 | json={ 71 | "rawLicenseRequestBase64": str(request, "utf-8" ), 72 | "puid": '339572866', 73 | "watchSessionId": 'ed0e355063ac48b783130a390dc27ba6', 74 | "contentId": '4315082489d87677b21f7c83593fcb73', 75 | "contentTypeId": '21', 76 | "serviceName": 'ott-kp', 77 | "productId": '2', 78 | "monetizationModel": 'SVOD', 79 | "expirationTimestamp": '1639009453', 80 | "verificationRequired": 'false', 81 | "signature": str(signature), 82 | "version": 'V4' 83 | })) 84 | for idx, response in enumerate(responses): 85 | try: 86 | str(response.content, "utf-8") 87 | except UnicodeDecodeError: 88 | widevine_license = response 89 | print(f'{chr(10)}license response status: {widevine_license}{chr(10)}') 90 | break 91 | else: 92 | if len(str(response.content, "utf-8")) > 500: 93 | widevine_license = response 94 | print(f'{chr(10)}license response status: {widevine_license}{chr(10)}') 95 | break 96 | if idx == len(responses) - 1: 97 | print(f'{chr(10)}license response status: {response}') 98 | print(f'server reports: {str(response.content, "utf-8")}') 99 | print(f'server did not issue license, make sure you have correctly pasted all the required headers in the headers.py. Also check json/raw params of POST request.{chr(10)}') 100 | exit() 101 | 102 | lic_field_names = ['license', 'payload', 'getWidevineLicenseResponse'] 103 | lic_field_names2 = ['license'] 104 | 105 | open('license_content.bin', 'wb').write(widevine_license.content) 106 | 107 | try: 108 | if str(widevine_license.content, 'utf-8').find(':'): 109 | for key in lic_field_names: 110 | try: 111 | license_b64 = json.loads(widevine_license.content.decode())[key] 112 | except: 113 | pass 114 | else: 115 | for key2 in lic_field_names2: 116 | try: 117 | license_b64 = json.loads(widevine_license.content.decode())[key][key2] 118 | except: 119 | pass 120 | else: 121 | license_b64 = widevine_license.content 122 | except: 123 | license_b64 = b64encode(widevine_license.content) 124 | 125 | wvdecrypt.update_license(license_b64) 126 | Correct, keyswvdecrypt = wvdecrypt.start_process() 127 | if Correct: 128 | return Correct, keyswvdecrypt 129 | 130 | correct, keys = WV_Function(pssh, lic_url) 131 | 132 | for key in keys: 133 | print('KID:KEY -> ' + key) 134 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/duration.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/durationpb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "DurationProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Duration represents a signed, fixed-length span of time represented 44 | // as a count of seconds and fractions of seconds at nanosecond 45 | // resolution. It is independent of any calendar and concepts like "day" 46 | // or "month". It is related to Timestamp in that the difference between 47 | // two Timestamp values is a Duration and it can be added or subtracted 48 | // from a Timestamp. Range is approximately +-10,000 years. 49 | // 50 | // # Examples 51 | // 52 | // Example 1: Compute Duration from two Timestamps in pseudo code. 53 | // 54 | // Timestamp start = ...; 55 | // Timestamp end = ...; 56 | // Duration duration = ...; 57 | // 58 | // duration.seconds = end.seconds - start.seconds; 59 | // duration.nanos = end.nanos - start.nanos; 60 | // 61 | // if (duration.seconds < 0 && duration.nanos > 0) { 62 | // duration.seconds += 1; 63 | // duration.nanos -= 1000000000; 64 | // } else if (duration.seconds > 0 && duration.nanos < 0) { 65 | // duration.seconds -= 1; 66 | // duration.nanos += 1000000000; 67 | // } 68 | // 69 | // Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 70 | // 71 | // Timestamp start = ...; 72 | // Duration duration = ...; 73 | // Timestamp end = ...; 74 | // 75 | // end.seconds = start.seconds + duration.seconds; 76 | // end.nanos = start.nanos + duration.nanos; 77 | // 78 | // if (end.nanos < 0) { 79 | // end.seconds -= 1; 80 | // end.nanos += 1000000000; 81 | // } else if (end.nanos >= 1000000000) { 82 | // end.seconds += 1; 83 | // end.nanos -= 1000000000; 84 | // } 85 | // 86 | // Example 3: Compute Duration from datetime.timedelta in Python. 87 | // 88 | // td = datetime.timedelta(days=3, minutes=10) 89 | // duration = Duration() 90 | // duration.FromTimedelta(td) 91 | // 92 | // # JSON Mapping 93 | // 94 | // In JSON format, the Duration type is encoded as a string rather than an 95 | // object, where the string ends in the suffix "s" (indicating seconds) and 96 | // is preceded by the number of seconds, with nanoseconds expressed as 97 | // fractional seconds. For example, 3 seconds with 0 nanoseconds should be 98 | // encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 99 | // be expressed in JSON format as "3.000000001s", and 3 seconds and 1 100 | // microsecond should be expressed in JSON format as "3.000001s". 101 | // 102 | // 103 | message Duration { 104 | // Signed seconds of the span of time. Must be from -315,576,000,000 105 | // to +315,576,000,000 inclusive. Note: these bounds are computed from: 106 | // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 107 | int64 seconds = 1; 108 | 109 | // Signed fractions of a second at nanosecond resolution of the span 110 | // of time. Durations less than one second are represented with a 0 111 | // `seconds` field and a positive or negative `nanos` field. For durations 112 | // of one second or more, a non-zero value for the `nanos` field must be 113 | // of the same sign as the `seconds` field. Must be from -999,999,999 114 | // to +999,999,999 inclusive. 115 | int32 nanos = 2; 116 | } 117 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/any.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option go_package = "google.golang.org/protobuf/types/known/anypb"; 37 | option java_package = "com.google.protobuf"; 38 | option java_outer_classname = "AnyProto"; 39 | option java_multiple_files = true; 40 | option objc_class_prefix = "GPB"; 41 | 42 | // `Any` contains an arbitrary serialized protocol buffer message along with a 43 | // URL that describes the type of the serialized message. 44 | // 45 | // Protobuf library provides support to pack/unpack Any values in the form 46 | // of utility functions or additional generated methods of the Any type. 47 | // 48 | // Example 1: Pack and unpack a message in C++. 49 | // 50 | // Foo foo = ...; 51 | // Any any; 52 | // any.PackFrom(foo); 53 | // ... 54 | // if (any.UnpackTo(&foo)) { 55 | // ... 56 | // } 57 | // 58 | // Example 2: Pack and unpack a message in Java. 59 | // 60 | // Foo foo = ...; 61 | // Any any = Any.pack(foo); 62 | // ... 63 | // if (any.is(Foo.class)) { 64 | // foo = any.unpack(Foo.class); 65 | // } 66 | // 67 | // Example 3: Pack and unpack a message in Python. 68 | // 69 | // foo = Foo(...) 70 | // any = Any() 71 | // any.Pack(foo) 72 | // ... 73 | // if any.Is(Foo.DESCRIPTOR): 74 | // any.Unpack(foo) 75 | // ... 76 | // 77 | // Example 4: Pack and unpack a message in Go 78 | // 79 | // foo := &pb.Foo{...} 80 | // any, err := anypb.New(foo) 81 | // if err != nil { 82 | // ... 83 | // } 84 | // ... 85 | // foo := &pb.Foo{} 86 | // if err := any.UnmarshalTo(foo); err != nil { 87 | // ... 88 | // } 89 | // 90 | // The pack methods provided by protobuf library will by default use 91 | // 'type.googleapis.com/full.type.name' as the type URL and the unpack 92 | // methods only use the fully qualified type name after the last '/' 93 | // in the type URL, for example "foo.bar.com/x/y.z" will yield type 94 | // name "y.z". 95 | // 96 | // 97 | // JSON 98 | // ==== 99 | // The JSON representation of an `Any` value uses the regular 100 | // representation of the deserialized, embedded message, with an 101 | // additional field `@type` which contains the type URL. Example: 102 | // 103 | // package google.profile; 104 | // message Person { 105 | // string first_name = 1; 106 | // string last_name = 2; 107 | // } 108 | // 109 | // { 110 | // "@type": "type.googleapis.com/google.profile.Person", 111 | // "firstName": , 112 | // "lastName": 113 | // } 114 | // 115 | // If the embedded message type is well-known and has a custom JSON 116 | // representation, that representation will be embedded adding a field 117 | // `value` which holds the custom JSON in addition to the `@type` 118 | // field. Example (for message [google.protobuf.Duration][]): 119 | // 120 | // { 121 | // "@type": "type.googleapis.com/google.protobuf.Duration", 122 | // "value": "1.212s" 123 | // } 124 | // 125 | message Any { 126 | // A URL/resource name that uniquely identifies the type of the serialized 127 | // protocol buffer message. This string must contain at least 128 | // one "/" character. The last segment of the URL's path must represent 129 | // the fully qualified name of the type (as in 130 | // `path/google.protobuf.Duration`). The name should be in a canonical form 131 | // (e.g., leading "." is not accepted). 132 | // 133 | // In practice, teams usually precompile into the binary all types that they 134 | // expect it to use in the context of Any. However, for URLs which use the 135 | // scheme `http`, `https`, or no scheme, one can optionally set up a type 136 | // server that maps type URLs to message definitions as follows: 137 | // 138 | // * If no scheme is provided, `https` is assumed. 139 | // * An HTTP GET on the URL must yield a [google.protobuf.Type][] 140 | // value in binary format, or produce an error. 141 | // * Applications are allowed to cache lookup results based on the 142 | // URL, or have them precompiled into a binary to avoid any 143 | // lookup. Therefore, binary compatibility needs to be preserved 144 | // on changes to types. (Use versioned type names to manage 145 | // breaking changes.) 146 | // 147 | // Note: this functionality is not currently available in the official 148 | // protobuf release, and it is not used for type URLs beginning with 149 | // type.googleapis.com. 150 | // 151 | // Schemes other than `http`, `https` (or the empty scheme) might be 152 | // used with implementation specific semantics. 153 | // 154 | string type_url = 1; 155 | 156 | // Must be a valid serialized protocol buffer of the above specified type. 157 | bytes value = 2; 158 | } 159 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/type.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | import "google/protobuf/any.proto"; 36 | import "google/protobuf/source_context.proto"; 37 | 38 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 39 | option cc_enable_arenas = true; 40 | option java_package = "com.google.protobuf"; 41 | option java_outer_classname = "TypeProto"; 42 | option java_multiple_files = true; 43 | option objc_class_prefix = "GPB"; 44 | option go_package = "google.golang.org/protobuf/types/known/typepb"; 45 | 46 | // A protocol buffer message type. 47 | message Type { 48 | // The fully qualified message name. 49 | string name = 1; 50 | // The list of fields. 51 | repeated Field fields = 2; 52 | // The list of types appearing in `oneof` definitions in this type. 53 | repeated string oneofs = 3; 54 | // The protocol buffer options. 55 | repeated Option options = 4; 56 | // The source context. 57 | SourceContext source_context = 5; 58 | // The source syntax. 59 | Syntax syntax = 6; 60 | } 61 | 62 | // A single field of a message type. 63 | message Field { 64 | // Basic field types. 65 | enum Kind { 66 | // Field type unknown. 67 | TYPE_UNKNOWN = 0; 68 | // Field type double. 69 | TYPE_DOUBLE = 1; 70 | // Field type float. 71 | TYPE_FLOAT = 2; 72 | // Field type int64. 73 | TYPE_INT64 = 3; 74 | // Field type uint64. 75 | TYPE_UINT64 = 4; 76 | // Field type int32. 77 | TYPE_INT32 = 5; 78 | // Field type fixed64. 79 | TYPE_FIXED64 = 6; 80 | // Field type fixed32. 81 | TYPE_FIXED32 = 7; 82 | // Field type bool. 83 | TYPE_BOOL = 8; 84 | // Field type string. 85 | TYPE_STRING = 9; 86 | // Field type group. Proto2 syntax only, and deprecated. 87 | TYPE_GROUP = 10; 88 | // Field type message. 89 | TYPE_MESSAGE = 11; 90 | // Field type bytes. 91 | TYPE_BYTES = 12; 92 | // Field type uint32. 93 | TYPE_UINT32 = 13; 94 | // Field type enum. 95 | TYPE_ENUM = 14; 96 | // Field type sfixed32. 97 | TYPE_SFIXED32 = 15; 98 | // Field type sfixed64. 99 | TYPE_SFIXED64 = 16; 100 | // Field type sint32. 101 | TYPE_SINT32 = 17; 102 | // Field type sint64. 103 | TYPE_SINT64 = 18; 104 | } 105 | 106 | // Whether a field is optional, required, or repeated. 107 | enum Cardinality { 108 | // For fields with unknown cardinality. 109 | CARDINALITY_UNKNOWN = 0; 110 | // For optional fields. 111 | CARDINALITY_OPTIONAL = 1; 112 | // For required fields. Proto2 syntax only. 113 | CARDINALITY_REQUIRED = 2; 114 | // For repeated fields. 115 | CARDINALITY_REPEATED = 3; 116 | } 117 | 118 | // The field type. 119 | Kind kind = 1; 120 | // The field cardinality. 121 | Cardinality cardinality = 2; 122 | // The field number. 123 | int32 number = 3; 124 | // The field name. 125 | string name = 4; 126 | // The field type URL, without the scheme, for message or enumeration 127 | // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. 128 | string type_url = 6; 129 | // The index of the field type in `Type.oneofs`, for message or enumeration 130 | // types. The first type has index 1; zero means the type is not in the list. 131 | int32 oneof_index = 7; 132 | // Whether to use alternative packed wire representation. 133 | bool packed = 8; 134 | // The protocol buffer options. 135 | repeated Option options = 9; 136 | // The field JSON name. 137 | string json_name = 10; 138 | // The string value of the default value of this field. Proto2 syntax only. 139 | string default_value = 11; 140 | } 141 | 142 | // Enum type definition. 143 | message Enum { 144 | // Enum type name. 145 | string name = 1; 146 | // Enum value definitions. 147 | repeated EnumValue enumvalue = 2; 148 | // Protocol buffer options. 149 | repeated Option options = 3; 150 | // The source context. 151 | SourceContext source_context = 4; 152 | // The source syntax. 153 | Syntax syntax = 5; 154 | } 155 | 156 | // Enum value definition. 157 | message EnumValue { 158 | // Enum value name. 159 | string name = 1; 160 | // Enum value number. 161 | int32 number = 2; 162 | // Protocol buffer options. 163 | repeated Option options = 3; 164 | } 165 | 166 | // A protocol buffer option, which can be attached to a message, field, 167 | // enumeration, etc. 168 | message Option { 169 | // The option's name. For protobuf built-in options (options defined in 170 | // descriptor.proto), this is the short name. For example, `"map_entry"`. 171 | // For custom options, it should be the fully-qualified name. For example, 172 | // `"google.api.http"`. 173 | string name = 1; 174 | // The option's value packed in an Any message. If the value is a primitive, 175 | // the corresponding wrapper type defined in google/protobuf/wrappers.proto 176 | // should be used. If the value is an enum, it should be stored as an int32 177 | // value using the google.protobuf.Int32Value type. 178 | Any value = 2; 179 | } 180 | 181 | // The syntax in which a protocol buffer element is defined. 182 | enum Syntax { 183 | // Syntax `proto2`. 184 | SYNTAX_PROTO2 = 0; 185 | // Syntax `proto3`. 186 | SYNTAX_PROTO3 = 1; 187 | } 188 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/timestamp.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option cc_enable_arenas = true; 37 | option go_package = "google.golang.org/protobuf/types/known/timestamppb"; 38 | option java_package = "com.google.protobuf"; 39 | option java_outer_classname = "TimestampProto"; 40 | option java_multiple_files = true; 41 | option objc_class_prefix = "GPB"; 42 | 43 | // A Timestamp represents a point in time independent of any time zone or local 44 | // calendar, encoded as a count of seconds and fractions of seconds at 45 | // nanosecond resolution. The count is relative to an epoch at UTC midnight on 46 | // January 1, 1970, in the proleptic Gregorian calendar which extends the 47 | // Gregorian calendar backwards to year one. 48 | // 49 | // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap 50 | // second table is needed for interpretation, using a [24-hour linear 51 | // smear](https://developers.google.com/time/smear). 52 | // 53 | // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By 54 | // restricting to that range, we ensure that we can convert to and from [RFC 55 | // 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. 56 | // 57 | // # Examples 58 | // 59 | // Example 1: Compute Timestamp from POSIX `time()`. 60 | // 61 | // Timestamp timestamp; 62 | // timestamp.set_seconds(time(NULL)); 63 | // timestamp.set_nanos(0); 64 | // 65 | // Example 2: Compute Timestamp from POSIX `gettimeofday()`. 66 | // 67 | // struct timeval tv; 68 | // gettimeofday(&tv, NULL); 69 | // 70 | // Timestamp timestamp; 71 | // timestamp.set_seconds(tv.tv_sec); 72 | // timestamp.set_nanos(tv.tv_usec * 1000); 73 | // 74 | // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 75 | // 76 | // FILETIME ft; 77 | // GetSystemTimeAsFileTime(&ft); 78 | // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 79 | // 80 | // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 81 | // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 82 | // Timestamp timestamp; 83 | // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 84 | // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 85 | // 86 | // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 87 | // 88 | // long millis = System.currentTimeMillis(); 89 | // 90 | // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 91 | // .setNanos((int) ((millis % 1000) * 1000000)).build(); 92 | // 93 | // 94 | // Example 5: Compute Timestamp from Java `Instant.now()`. 95 | // 96 | // Instant now = Instant.now(); 97 | // 98 | // Timestamp timestamp = 99 | // Timestamp.newBuilder().setSeconds(now.getEpochSecond()) 100 | // .setNanos(now.getNano()).build(); 101 | // 102 | // 103 | // Example 6: Compute Timestamp from current time in Python. 104 | // 105 | // timestamp = Timestamp() 106 | // timestamp.GetCurrentTime() 107 | // 108 | // # JSON Mapping 109 | // 110 | // In JSON format, the Timestamp type is encoded as a string in the 111 | // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 112 | // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 113 | // where {year} is always expressed using four digits while {month}, {day}, 114 | // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 115 | // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 116 | // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 117 | // is required. A proto3 JSON serializer should always use UTC (as indicated by 118 | // "Z") when printing the Timestamp type and a proto3 JSON parser should be 119 | // able to accept both UTC and other timezones (as indicated by an offset). 120 | // 121 | // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 122 | // 01:30 UTC on January 15, 2017. 123 | // 124 | // In JavaScript, one can convert a Date object to this format using the 125 | // standard 126 | // [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) 127 | // method. In Python, a standard `datetime.datetime` object can be converted 128 | // to this format using 129 | // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with 130 | // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use 131 | // the Joda Time's [`ISODateTimeFormat.dateTime()`]( 132 | // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D 133 | // ) to obtain a formatter capable of generating timestamps in this format. 134 | // 135 | // 136 | message Timestamp { 137 | // Represents seconds of UTC time since Unix epoch 138 | // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 139 | // 9999-12-31T23:59:59Z inclusive. 140 | int64 seconds = 1; 141 | 142 | // Non-negative fractions of a second at nanosecond resolution. Negative 143 | // second values with fractions must still have non-negative nanos values 144 | // that count forward in time. Must be from 0 to 999,999,999 145 | // inclusive. 146 | int32 nanos = 2; 147 | } 148 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/api.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | import "google/protobuf/source_context.proto"; 36 | import "google/protobuf/type.proto"; 37 | 38 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 39 | option java_package = "com.google.protobuf"; 40 | option java_outer_classname = "ApiProto"; 41 | option java_multiple_files = true; 42 | option objc_class_prefix = "GPB"; 43 | option go_package = "google.golang.org/protobuf/types/known/apipb"; 44 | 45 | // Api is a light-weight descriptor for an API Interface. 46 | // 47 | // Interfaces are also described as "protocol buffer services" in some contexts, 48 | // such as by the "service" keyword in a .proto file, but they are different 49 | // from API Services, which represent a concrete implementation of an interface 50 | // as opposed to simply a description of methods and bindings. They are also 51 | // sometimes simply referred to as "APIs" in other contexts, such as the name of 52 | // this message itself. See https://cloud.google.com/apis/design/glossary for 53 | // detailed terminology. 54 | message Api { 55 | // The fully qualified name of this interface, including package name 56 | // followed by the interface's simple name. 57 | string name = 1; 58 | 59 | // The methods of this interface, in unspecified order. 60 | repeated Method methods = 2; 61 | 62 | // Any metadata attached to the interface. 63 | repeated Option options = 3; 64 | 65 | // A version string for this interface. If specified, must have the form 66 | // `major-version.minor-version`, as in `1.10`. If the minor version is 67 | // omitted, it defaults to zero. If the entire version field is empty, the 68 | // major version is derived from the package name, as outlined below. If the 69 | // field is not empty, the version in the package name will be verified to be 70 | // consistent with what is provided here. 71 | // 72 | // The versioning schema uses [semantic 73 | // versioning](http://semver.org) where the major version number 74 | // indicates a breaking change and the minor version an additive, 75 | // non-breaking change. Both version numbers are signals to users 76 | // what to expect from different versions, and should be carefully 77 | // chosen based on the product plan. 78 | // 79 | // The major version is also reflected in the package name of the 80 | // interface, which must end in `v`, as in 81 | // `google.feature.v1`. For major versions 0 and 1, the suffix can 82 | // be omitted. Zero major versions must only be used for 83 | // experimental, non-GA interfaces. 84 | // 85 | // 86 | string version = 4; 87 | 88 | // Source context for the protocol buffer service represented by this 89 | // message. 90 | SourceContext source_context = 5; 91 | 92 | // Included interfaces. See [Mixin][]. 93 | repeated Mixin mixins = 6; 94 | 95 | // The source syntax of the service. 96 | Syntax syntax = 7; 97 | } 98 | 99 | // Method represents a method of an API interface. 100 | message Method { 101 | // The simple name of this method. 102 | string name = 1; 103 | 104 | // A URL of the input message type. 105 | string request_type_url = 2; 106 | 107 | // If true, the request is streamed. 108 | bool request_streaming = 3; 109 | 110 | // The URL of the output message type. 111 | string response_type_url = 4; 112 | 113 | // If true, the response is streamed. 114 | bool response_streaming = 5; 115 | 116 | // Any metadata attached to the method. 117 | repeated Option options = 6; 118 | 119 | // The source syntax of this method. 120 | Syntax syntax = 7; 121 | } 122 | 123 | // Declares an API Interface to be included in this interface. The including 124 | // interface must redeclare all the methods from the included interface, but 125 | // documentation and options are inherited as follows: 126 | // 127 | // - If after comment and whitespace stripping, the documentation 128 | // string of the redeclared method is empty, it will be inherited 129 | // from the original method. 130 | // 131 | // - Each annotation belonging to the service config (http, 132 | // visibility) which is not set in the redeclared method will be 133 | // inherited. 134 | // 135 | // - If an http annotation is inherited, the path pattern will be 136 | // modified as follows. Any version prefix will be replaced by the 137 | // version of the including interface plus the [root][] path if 138 | // specified. 139 | // 140 | // Example of a simple mixin: 141 | // 142 | // package google.acl.v1; 143 | // service AccessControl { 144 | // // Get the underlying ACL object. 145 | // rpc GetAcl(GetAclRequest) returns (Acl) { 146 | // option (google.api.http).get = "/v1/{resource=**}:getAcl"; 147 | // } 148 | // } 149 | // 150 | // package google.storage.v2; 151 | // service Storage { 152 | // rpc GetAcl(GetAclRequest) returns (Acl); 153 | // 154 | // // Get a data record. 155 | // rpc GetData(GetDataRequest) returns (Data) { 156 | // option (google.api.http).get = "/v2/{resource=**}"; 157 | // } 158 | // } 159 | // 160 | // Example of a mixin configuration: 161 | // 162 | // apis: 163 | // - name: google.storage.v2.Storage 164 | // mixins: 165 | // - name: google.acl.v1.AccessControl 166 | // 167 | // The mixin construct implies that all methods in `AccessControl` are 168 | // also declared with same name and request/response types in 169 | // `Storage`. A documentation generator or annotation processor will 170 | // see the effective `Storage.GetAcl` method after inheriting 171 | // documentation and annotations as follows: 172 | // 173 | // service Storage { 174 | // // Get the underlying ACL object. 175 | // rpc GetAcl(GetAclRequest) returns (Acl) { 176 | // option (google.api.http).get = "/v2/{resource=**}:getAcl"; 177 | // } 178 | // ... 179 | // } 180 | // 181 | // Note how the version in the path pattern changed from `v1` to `v2`. 182 | // 183 | // If the `root` field in the mixin is specified, it should be a 184 | // relative path under which inherited HTTP paths are placed. Example: 185 | // 186 | // apis: 187 | // - name: google.storage.v2.Storage 188 | // mixins: 189 | // - name: google.acl.v1.AccessControl 190 | // root: acls 191 | // 192 | // This implies the following inherited HTTP annotation: 193 | // 194 | // service Storage { 195 | // // Get the underlying ACL object. 196 | // rpc GetAcl(GetAclRequest) returns (Acl) { 197 | // option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; 198 | // } 199 | // ... 200 | // } 201 | message Mixin { 202 | // The fully qualified name of the interface which is included. 203 | string name = 1; 204 | 205 | // If non-empty specifies a path under which inherited HTTP paths 206 | // are rooted. 207 | string root = 2; 208 | } 209 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/field_mask.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "FieldMaskProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; 41 | option cc_enable_arenas = true; 42 | 43 | // `FieldMask` represents a set of symbolic field paths, for example: 44 | // 45 | // paths: "f.a" 46 | // paths: "f.b.d" 47 | // 48 | // Here `f` represents a field in some root message, `a` and `b` 49 | // fields in the message found in `f`, and `d` a field found in the 50 | // message in `f.b`. 51 | // 52 | // Field masks are used to specify a subset of fields that should be 53 | // returned by a get operation or modified by an update operation. 54 | // Field masks also have a custom JSON encoding (see below). 55 | // 56 | // # Field Masks in Projections 57 | // 58 | // When used in the context of a projection, a response message or 59 | // sub-message is filtered by the API to only contain those fields as 60 | // specified in the mask. For example, if the mask in the previous 61 | // example is applied to a response message as follows: 62 | // 63 | // f { 64 | // a : 22 65 | // b { 66 | // d : 1 67 | // x : 2 68 | // } 69 | // y : 13 70 | // } 71 | // z: 8 72 | // 73 | // The result will not contain specific values for fields x,y and z 74 | // (their value will be set to the default, and omitted in proto text 75 | // output): 76 | // 77 | // 78 | // f { 79 | // a : 22 80 | // b { 81 | // d : 1 82 | // } 83 | // } 84 | // 85 | // A repeated field is not allowed except at the last position of a 86 | // paths string. 87 | // 88 | // If a FieldMask object is not present in a get operation, the 89 | // operation applies to all fields (as if a FieldMask of all fields 90 | // had been specified). 91 | // 92 | // Note that a field mask does not necessarily apply to the 93 | // top-level response message. In case of a REST get operation, the 94 | // field mask applies directly to the response, but in case of a REST 95 | // list operation, the mask instead applies to each individual message 96 | // in the returned resource list. In case of a REST custom method, 97 | // other definitions may be used. Where the mask applies will be 98 | // clearly documented together with its declaration in the API. In 99 | // any case, the effect on the returned resource/resources is required 100 | // behavior for APIs. 101 | // 102 | // # Field Masks in Update Operations 103 | // 104 | // A field mask in update operations specifies which fields of the 105 | // targeted resource are going to be updated. The API is required 106 | // to only change the values of the fields as specified in the mask 107 | // and leave the others untouched. If a resource is passed in to 108 | // describe the updated values, the API ignores the values of all 109 | // fields not covered by the mask. 110 | // 111 | // If a repeated field is specified for an update operation, new values will 112 | // be appended to the existing repeated field in the target resource. Note that 113 | // a repeated field is only allowed in the last position of a `paths` string. 114 | // 115 | // If a sub-message is specified in the last position of the field mask for an 116 | // update operation, then new value will be merged into the existing sub-message 117 | // in the target resource. 118 | // 119 | // For example, given the target message: 120 | // 121 | // f { 122 | // b { 123 | // d: 1 124 | // x: 2 125 | // } 126 | // c: [1] 127 | // } 128 | // 129 | // And an update message: 130 | // 131 | // f { 132 | // b { 133 | // d: 10 134 | // } 135 | // c: [2] 136 | // } 137 | // 138 | // then if the field mask is: 139 | // 140 | // paths: ["f.b", "f.c"] 141 | // 142 | // then the result will be: 143 | // 144 | // f { 145 | // b { 146 | // d: 10 147 | // x: 2 148 | // } 149 | // c: [1, 2] 150 | // } 151 | // 152 | // An implementation may provide options to override this default behavior for 153 | // repeated and message fields. 154 | // 155 | // In order to reset a field's value to the default, the field must 156 | // be in the mask and set to the default value in the provided resource. 157 | // Hence, in order to reset all fields of a resource, provide a default 158 | // instance of the resource and set all fields in the mask, or do 159 | // not provide a mask as described below. 160 | // 161 | // If a field mask is not present on update, the operation applies to 162 | // all fields (as if a field mask of all fields has been specified). 163 | // Note that in the presence of schema evolution, this may mean that 164 | // fields the client does not know and has therefore not filled into 165 | // the request will be reset to their default. If this is unwanted 166 | // behavior, a specific service may require a client to always specify 167 | // a field mask, producing an error if not. 168 | // 169 | // As with get operations, the location of the resource which 170 | // describes the updated values in the request message depends on the 171 | // operation kind. In any case, the effect of the field mask is 172 | // required to be honored by the API. 173 | // 174 | // ## Considerations for HTTP REST 175 | // 176 | // The HTTP kind of an update operation which uses a field mask must 177 | // be set to PATCH instead of PUT in order to satisfy HTTP semantics 178 | // (PUT must only be used for full updates). 179 | // 180 | // # JSON Encoding of Field Masks 181 | // 182 | // In JSON, a field mask is encoded as a single string where paths are 183 | // separated by a comma. Fields name in each path are converted 184 | // to/from lower-camel naming conventions. 185 | // 186 | // As an example, consider the following message declarations: 187 | // 188 | // message Profile { 189 | // User user = 1; 190 | // Photo photo = 2; 191 | // } 192 | // message User { 193 | // string display_name = 1; 194 | // string address = 2; 195 | // } 196 | // 197 | // In proto a field mask for `Profile` may look as such: 198 | // 199 | // mask { 200 | // paths: "user.display_name" 201 | // paths: "photo" 202 | // } 203 | // 204 | // In JSON, the same mask is represented as below: 205 | // 206 | // { 207 | // mask: "user.displayName,photo" 208 | // } 209 | // 210 | // # Field Masks and Oneof Fields 211 | // 212 | // Field masks treat fields in oneofs just as regular fields. Consider the 213 | // following message: 214 | // 215 | // message SampleMessage { 216 | // oneof test_oneof { 217 | // string name = 4; 218 | // SubMessage sub_message = 9; 219 | // } 220 | // } 221 | // 222 | // The field mask can be: 223 | // 224 | // mask { 225 | // paths: "name" 226 | // } 227 | // 228 | // Or: 229 | // 230 | // mask { 231 | // paths: "sub_message" 232 | // } 233 | // 234 | // Note that oneof type names ("test_oneof" in this case) cannot be used in 235 | // paths. 236 | // 237 | // ## Field Mask Verification 238 | // 239 | // The implementation of any API method which has a FieldMask type field in the 240 | // request should verify the included field paths, and return an 241 | // `INVALID_ARGUMENT` error if any path is unmappable. 242 | message FieldMask { 243 | // The set of field mask paths. 244 | repeated string paths = 1; 245 | } 246 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/compiler/plugin.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Author: kenton@google.com (Kenton Varda) 32 | // 33 | // WARNING: The plugin interface is currently EXPERIMENTAL and is subject to 34 | // change. 35 | // 36 | // protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is 37 | // just a program that reads a CodeGeneratorRequest from stdin and writes a 38 | // CodeGeneratorResponse to stdout. 39 | // 40 | // Plugins written using C++ can use google/protobuf/compiler/plugin.h instead 41 | // of dealing with the raw protocol defined here. 42 | // 43 | // A plugin executable needs only to be placed somewhere in the path. The 44 | // plugin should be named "protoc-gen-$NAME", and will then be used when the 45 | // flag "--${NAME}_out" is passed to protoc. 46 | 47 | syntax = "proto2"; 48 | 49 | package google.protobuf.compiler; 50 | option java_package = "com.google.protobuf.compiler"; 51 | option java_outer_classname = "PluginProtos"; 52 | 53 | option go_package = "google.golang.org/protobuf/types/pluginpb"; 54 | 55 | import "google/protobuf/descriptor.proto"; 56 | 57 | // The version number of protocol compiler. 58 | message Version { 59 | optional int32 major = 1; 60 | optional int32 minor = 2; 61 | optional int32 patch = 3; 62 | // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should 63 | // be empty for mainline stable releases. 64 | optional string suffix = 4; 65 | } 66 | 67 | // An encoded CodeGeneratorRequest is written to the plugin's stdin. 68 | message CodeGeneratorRequest { 69 | // The .proto files that were explicitly listed on the command-line. The 70 | // code generator should generate code only for these files. Each file's 71 | // descriptor will be included in proto_file, below. 72 | repeated string file_to_generate = 1; 73 | 74 | // The generator parameter passed on the command-line. 75 | optional string parameter = 2; 76 | 77 | // FileDescriptorProtos for all files in files_to_generate and everything 78 | // they import. The files will appear in topological order, so each file 79 | // appears before any file that imports it. 80 | // 81 | // protoc guarantees that all proto_files will be written after 82 | // the fields above, even though this is not technically guaranteed by the 83 | // protobuf wire format. This theoretically could allow a plugin to stream 84 | // in the FileDescriptorProtos and handle them one by one rather than read 85 | // the entire set into memory at once. However, as of this writing, this 86 | // is not similarly optimized on protoc's end -- it will store all fields in 87 | // memory at once before sending them to the plugin. 88 | // 89 | // Type names of fields and extensions in the FileDescriptorProto are always 90 | // fully qualified. 91 | repeated FileDescriptorProto proto_file = 15; 92 | 93 | // The version number of protocol compiler. 94 | optional Version compiler_version = 3; 95 | 96 | } 97 | 98 | // The plugin writes an encoded CodeGeneratorResponse to stdout. 99 | message CodeGeneratorResponse { 100 | // Error message. If non-empty, code generation failed. The plugin process 101 | // should exit with status code zero even if it reports an error in this way. 102 | // 103 | // This should be used to indicate errors in .proto files which prevent the 104 | // code generator from generating correct code. Errors which indicate a 105 | // problem in protoc itself -- such as the input CodeGeneratorRequest being 106 | // unparseable -- should be reported by writing a message to stderr and 107 | // exiting with a non-zero status code. 108 | optional string error = 1; 109 | 110 | // A bitmask of supported features that the code generator supports. 111 | // This is a bitwise "or" of values from the Feature enum. 112 | optional uint64 supported_features = 2; 113 | 114 | // Sync with code_generator.h. 115 | enum Feature { 116 | FEATURE_NONE = 0; 117 | FEATURE_PROTO3_OPTIONAL = 1; 118 | } 119 | 120 | // Represents a single generated file. 121 | message File { 122 | // The file name, relative to the output directory. The name must not 123 | // contain "." or ".." components and must be relative, not be absolute (so, 124 | // the file cannot lie outside the output directory). "/" must be used as 125 | // the path separator, not "\". 126 | // 127 | // If the name is omitted, the content will be appended to the previous 128 | // file. This allows the generator to break large files into small chunks, 129 | // and allows the generated text to be streamed back to protoc so that large 130 | // files need not reside completely in memory at one time. Note that as of 131 | // this writing protoc does not optimize for this -- it will read the entire 132 | // CodeGeneratorResponse before writing files to disk. 133 | optional string name = 1; 134 | 135 | // If non-empty, indicates that the named file should already exist, and the 136 | // content here is to be inserted into that file at a defined insertion 137 | // point. This feature allows a code generator to extend the output 138 | // produced by another code generator. The original generator may provide 139 | // insertion points by placing special annotations in the file that look 140 | // like: 141 | // @@protoc_insertion_point(NAME) 142 | // The annotation can have arbitrary text before and after it on the line, 143 | // which allows it to be placed in a comment. NAME should be replaced with 144 | // an identifier naming the point -- this is what other generators will use 145 | // as the insertion_point. Code inserted at this point will be placed 146 | // immediately above the line containing the insertion point (thus multiple 147 | // insertions to the same point will come out in the order they were added). 148 | // The double-@ is intended to make it unlikely that the generated code 149 | // could contain things that look like insertion points by accident. 150 | // 151 | // For example, the C++ code generator places the following line in the 152 | // .pb.h files that it generates: 153 | // // @@protoc_insertion_point(namespace_scope) 154 | // This line appears within the scope of the file's package namespace, but 155 | // outside of any particular class. Another plugin can then specify the 156 | // insertion_point "namespace_scope" to generate additional classes or 157 | // other declarations that should be placed in this scope. 158 | // 159 | // Note that if the line containing the insertion point begins with 160 | // whitespace, the same whitespace will be added to every line of the 161 | // inserted text. This is useful for languages like Python, where 162 | // indentation matters. In these languages, the insertion point comment 163 | // should be indented the same amount as any inserted code will need to be 164 | // in order to work correctly in that context. 165 | // 166 | // The code generator that generates the initial file and the one which 167 | // inserts into it must both run as part of a single invocation of protoc. 168 | // Code generators are executed in the order in which they appear on the 169 | // command line. 170 | // 171 | // If |insertion_point| is present, |name| must also be present. 172 | optional string insertion_point = 2; 173 | 174 | // The file contents. 175 | optional string content = 15; 176 | 177 | // Information describing the file content being inserted. If an insertion 178 | // point is used, this information will be appropriately offset and inserted 179 | // into the code generation metadata for the generated files. 180 | optional GeneratedCodeInfo generated_code_info = 16; 181 | } 182 | repeated File file = 15; 183 | } 184 | -------------------------------------------------------------------------------- /cdm/formats/wv_proto3.proto: -------------------------------------------------------------------------------- 1 | // beware proto3 won't show missing fields it seems, need to change to "proto2" and add "optional" before every field, and remove all the dummy enum members I added: 2 | syntax = "proto3"; 3 | 4 | // from x86 (partial), most of it from the ARM version: 5 | message ClientIdentification { 6 | enum TokenType { 7 | KEYBOX = 0; 8 | DEVICE_CERTIFICATE = 1; 9 | REMOTE_ATTESTATION_CERTIFICATE = 2; 10 | } 11 | message NameValue { 12 | string Name = 1; 13 | string Value = 2; 14 | } 15 | message ClientCapabilities { 16 | enum HdcpVersion { 17 | HDCP_NONE = 0; 18 | HDCP_V1 = 1; 19 | HDCP_V2 = 2; 20 | HDCP_V2_1 = 3; 21 | HDCP_V2_2 = 4; 22 | } 23 | uint32 ClientToken = 1; 24 | uint32 SessionToken = 2; 25 | uint32 VideoResolutionConstraints = 3; 26 | HdcpVersion MaxHdcpVersion = 4; 27 | uint32 OemCryptoApiVersion = 5; 28 | } 29 | TokenType Type = 1; 30 | //bytes Token = 2; // by default the client treats this as blob, but it's usually a DeviceCertificate, so for usefulness sake, I'm replacing it with this one: 31 | SignedDeviceCertificate Token = 2; 32 | repeated NameValue ClientInfo = 3; 33 | bytes ProviderClientToken = 4; 34 | uint32 LicenseCounter = 5; 35 | ClientCapabilities _ClientCapabilities = 6; // how should we deal with duped names? will have to look at proto docs later 36 | } 37 | 38 | message DeviceCertificate { 39 | enum CertificateType { 40 | ROOT = 0; 41 | INTERMEDIATE = 1; 42 | USER_DEVICE = 2; 43 | SERVICE = 3; 44 | } 45 | //ProvisionedDeviceInfo.WvSecurityLevel Type = 1; // is this how one is supposed to call it? (it's an enum) there might be a bug here, with CertificateType getting confused with WvSecurityLevel, for now renaming it (verify against other binaries) 46 | CertificateType Type = 1; 47 | bytes SerialNumber = 2; 48 | uint32 CreationTimeSeconds = 3; 49 | bytes PublicKey = 4; 50 | uint32 SystemId = 5; 51 | uint32 TestDeviceDeprecated = 6; // is it bool or int? 52 | bytes ServiceId = 7; // service URL for service certificates 53 | } 54 | 55 | // missing some references, 56 | message DeviceCertificateStatus { 57 | enum CertificateStatus { 58 | VALID = 0; 59 | REVOKED = 1; 60 | } 61 | bytes SerialNumber = 1; 62 | CertificateStatus Status = 2; 63 | ProvisionedDeviceInfo DeviceInfo = 4; // where is 3? is it deprecated? 64 | } 65 | 66 | message DeviceCertificateStatusList { 67 | uint32 CreationTimeSeconds = 1; 68 | repeated DeviceCertificateStatus CertificateStatus = 2; 69 | } 70 | 71 | message EncryptedClientIdentification { 72 | string ServiceId = 1; 73 | bytes ServiceCertificateSerialNumber = 2; 74 | bytes EncryptedClientId = 3; 75 | bytes EncryptedClientIdIv = 4; 76 | bytes EncryptedPrivacyKey = 5; 77 | } 78 | 79 | // todo: fill (for this top-level type, it might be impossible/difficult) 80 | enum LicenseType { 81 | ZERO = 0; 82 | DEFAULT = 1; // do not know what this is either, but should be 1; on recent versions may go up to 3 (latest x86) 83 | } 84 | 85 | // todo: fill (for this top-level type, it might be impossible/difficult) 86 | // this is just a guess because these globals got lost, but really, do we need more? 87 | enum ProtocolVersion { 88 | DUMMY = 0; 89 | CURRENT = 21; // don't have symbols for this 90 | } 91 | 92 | 93 | message LicenseIdentification { 94 | bytes RequestId = 1; 95 | bytes SessionId = 2; 96 | bytes PurchaseId = 3; 97 | LicenseType Type = 4; 98 | uint32 Version = 5; 99 | bytes ProviderSessionToken = 6; 100 | } 101 | 102 | 103 | message License { 104 | message Policy { 105 | uint32 CanPlay = 1; 106 | uint32 CanPersist = 2; 107 | uint32 CanRenew = 3; 108 | uint32 RentalDurationSeconds = 4; 109 | uint32 PlaybackDurationSeconds = 5; 110 | uint32 LicenseDurationSeconds = 6; 111 | uint32 RenewalRecoveryDurationSeconds = 7; 112 | string RenewalServerUrl = 8; 113 | uint32 RenewalDelaySeconds = 9; 114 | uint32 RenewalRetryIntervalSeconds = 10; 115 | uint32 RenewWithUsage = 11; 116 | uint32 UnknownPolicy12 = 12; 117 | } 118 | message KeyContainer { 119 | enum KeyType { 120 | _NOKEYTYPE = 0; // dummy, added to satisfy proto3, not present in original 121 | SIGNING = 1; 122 | CONTENT = 2; 123 | KEY_CONTROL = 3; 124 | OPERATOR_SESSION = 4; 125 | } 126 | enum SecurityLevel { 127 | _NOSECLEVEL = 0; // dummy, added to satisfy proto3, not present in original 128 | SW_SECURE_CRYPTO = 1; 129 | SW_SECURE_DECODE = 2; 130 | HW_SECURE_CRYPTO = 3; 131 | HW_SECURE_DECODE = 4; 132 | HW_SECURE_ALL = 5; 133 | } 134 | message OutputProtection { 135 | enum CGMS { 136 | COPY_FREE = 0; 137 | COPY_ONCE = 2; 138 | COPY_NEVER = 3; 139 | CGMS_NONE = 0x2A; // PC default! 140 | } 141 | ClientIdentification.ClientCapabilities.HdcpVersion Hdcp = 1; // it's most likely a copy of Hdcp version available here, but compiler optimized it away 142 | CGMS CgmsFlags = 2; 143 | } 144 | message KeyControl { 145 | bytes KeyControlBlock = 1; // what is this? 146 | bytes Iv = 2; 147 | } 148 | message OperatorSessionKeyPermissions { 149 | uint32 AllowEncrypt = 1; 150 | uint32 AllowDecrypt = 2; 151 | uint32 AllowSign = 3; 152 | uint32 AllowSignatureVerify = 4; 153 | } 154 | message VideoResolutionConstraint { 155 | uint32 MinResolutionPixels = 1; 156 | uint32 MaxResolutionPixels = 2; 157 | OutputProtection RequiredProtection = 3; 158 | } 159 | bytes Id = 1; 160 | bytes Iv = 2; 161 | bytes Key = 3; 162 | KeyType Type = 4; 163 | SecurityLevel Level = 5; 164 | OutputProtection RequiredProtection = 6; 165 | OutputProtection RequestedProtection = 7; 166 | KeyControl _KeyControl = 8; // duped names, etc 167 | OperatorSessionKeyPermissions _OperatorSessionKeyPermissions = 9; // duped names, etc 168 | repeated VideoResolutionConstraint VideoResolutionConstraints = 10; 169 | } 170 | LicenseIdentification Id = 1; 171 | Policy _Policy = 2; // duped names, etc 172 | repeated KeyContainer Key = 3; 173 | uint32 LicenseStartTime = 4; 174 | uint32 RemoteAttestationVerified = 5; // bool? 175 | bytes ProviderClientToken = 6; 176 | // there might be more, check with newer versions (I see field 7-8 in a lic) 177 | // this appeared in latest x86: 178 | uint32 ProtectionScheme = 7; // type unconfirmed fully, but it's likely as WidevineCencHeader describesit (fourcc) 179 | bytes UnknownHdcpDataField = 8; 180 | } 181 | 182 | message LicenseError { 183 | enum Error { 184 | DUMMY_NO_ERROR = 0; // dummy, added to satisfy proto3 185 | INVALID_DEVICE_CERTIFICATE = 1; 186 | REVOKED_DEVICE_CERTIFICATE = 2; 187 | SERVICE_UNAVAILABLE = 3; 188 | } 189 | //LicenseRequest.RequestType ErrorCode; // clang mismatch 190 | Error ErrorCode = 1; 191 | } 192 | 193 | message LicenseRequest { 194 | message ContentIdentification { 195 | message CENC { 196 | // bytes Pssh = 1; // the client's definition is opaque, it doesn't care about the contents, but the PSSH has a clear definition that is understood and requested by the server, thus I'll replace it with: 197 | WidevineCencHeader Pssh = 1; 198 | LicenseType LicenseType = 2; // unfortunately the LicenseType symbols are not present, acceptable value seems to only be 1 199 | bytes RequestId = 3; 200 | } 201 | message WebM { 202 | bytes Header = 1; // identical to CENC, aside from PSSH and the parent field number used 203 | LicenseType LicenseType = 2; 204 | bytes RequestId = 3; 205 | } 206 | message ExistingLicense { 207 | LicenseIdentification LicenseId = 1; 208 | uint32 SecondsSinceStarted = 2; 209 | uint32 SecondsSinceLastPlayed = 3; 210 | bytes SessionUsageTableEntry = 4; 211 | } 212 | CENC CencId = 1; 213 | WebM WebmId = 2; 214 | ExistingLicense License = 3; 215 | } 216 | enum RequestType { 217 | DUMMY_REQ_TYPE = 0; // dummy, added to satisfy proto3 218 | NEW = 1; 219 | RENEWAL = 2; 220 | RELEASE = 3; 221 | } 222 | ClientIdentification ClientId = 1; 223 | ContentIdentification ContentId = 2; 224 | RequestType Type = 3; 225 | uint32 RequestTime = 4; 226 | bytes KeyControlNonceDeprecated = 5; 227 | ProtocolVersion ProtocolVersion = 6; // lacking symbols for this 228 | uint32 KeyControlNonce = 7; 229 | EncryptedClientIdentification EncryptedClientId = 8; 230 | } 231 | 232 | message ProvisionedDeviceInfo { 233 | enum WvSecurityLevel { 234 | LEVEL_UNSPECIFIED = 0; 235 | LEVEL_1 = 1; 236 | LEVEL_2 = 2; 237 | LEVEL_3 = 3; 238 | } 239 | uint32 SystemId = 1; 240 | string Soc = 2; 241 | string Manufacturer = 3; 242 | string Model = 4; 243 | string DeviceType = 5; 244 | uint32 ModelYear = 6; 245 | WvSecurityLevel SecurityLevel = 7; 246 | uint32 TestDevice = 8; // bool? 247 | } 248 | 249 | 250 | // todo: fill 251 | message ProvisioningOptions { 252 | } 253 | 254 | // todo: fill 255 | message ProvisioningRequest { 256 | } 257 | 258 | // todo: fill 259 | message ProvisioningResponse { 260 | } 261 | 262 | message RemoteAttestation { 263 | EncryptedClientIdentification Certificate = 1; 264 | string Salt = 2; 265 | string Signature = 3; 266 | } 267 | 268 | // todo: fill 269 | message SessionInit { 270 | } 271 | 272 | // todo: fill 273 | message SessionState { 274 | } 275 | 276 | // todo: fill 277 | message SignedCertificateStatusList { 278 | } 279 | 280 | message SignedDeviceCertificate { 281 | 282 | //bytes DeviceCertificate = 1; // again, they use a buffer where it's supposed to be a message, so we'll replace it with what it really is: 283 | DeviceCertificate _DeviceCertificate = 1; // how should we deal with duped names? will have to look at proto docs later 284 | bytes Signature = 2; 285 | SignedDeviceCertificate Signer = 3; 286 | } 287 | 288 | 289 | // todo: fill 290 | message SignedProvisioningMessage { 291 | } 292 | 293 | // the root of all messages, from either server or client 294 | message SignedMessage { 295 | enum MessageType { 296 | DUMMY_MSG_TYPE = 0; // dummy, added to satisfy proto3 297 | LICENSE_REQUEST = 1; 298 | LICENSE = 2; 299 | ERROR_RESPONSE = 3; 300 | SERVICE_CERTIFICATE_REQUEST = 4; 301 | SERVICE_CERTIFICATE = 5; 302 | } 303 | MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 304 | bytes Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 305 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 306 | bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 307 | bytes SessionKey = 4; // often RSA wrapped for licenses 308 | RemoteAttestation RemoteAttestation = 5; 309 | } 310 | 311 | 312 | 313 | // This message is copied from google's docs, not reversed: 314 | message WidevineCencHeader { 315 | enum Algorithm { 316 | UNENCRYPTED = 0; 317 | AESCTR = 1; 318 | }; 319 | Algorithm algorithm = 1; 320 | repeated bytes key_id = 2; 321 | 322 | // Content provider name. 323 | string provider = 3; 324 | 325 | // A content identifier, specified by content provider. 326 | bytes content_id = 4; 327 | 328 | // Track type. Acceptable values are SD, HD and AUDIO. Used to 329 | // differentiate content keys used by an asset. 330 | string track_type_deprecated = 5; 331 | 332 | // The name of a registered policy to be used for this asset. 333 | string policy = 6; 334 | 335 | // Crypto period index, for media using key rotation. 336 | uint32 crypto_period_index = 7; 337 | 338 | // Optional protected context for group content. The grouped_license is a 339 | // serialized SignedMessage. 340 | bytes grouped_license = 8; 341 | 342 | // Protection scheme identifying the encryption algorithm. 343 | // Represented as one of the following 4CC values: 344 | // 'cenc' (AESCTR), 'cbc1' (AESCBC), 345 | // 'cens' (AESCTR subsample), 'cbcs' (AESCBC subsample). 346 | uint32 protection_scheme = 9; 347 | 348 | // Optional. For media using key rotation, this represents the duration 349 | // of each crypto period in seconds. 350 | uint32 crypto_period_seconds = 10; 351 | } 352 | 353 | 354 | 355 | 356 | // from here on, it's just for testing, these messages don't exist in the binaries, I'm adding them to avoid detecting type programmatically 357 | message SignedLicenseRequest { 358 | enum MessageType { 359 | DUMMY_MSG_TYPE = 0; // dummy, added to satisfy proto3 360 | LICENSE_REQUEST = 1; 361 | LICENSE = 2; 362 | ERROR_RESPONSE = 3; 363 | SERVICE_CERTIFICATE_REQUEST = 4; 364 | SERVICE_CERTIFICATE = 5; 365 | } 366 | MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 367 | LicenseRequest Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 368 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 369 | string Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 370 | bytes SessionKey = 4; // often RSA wrapped for licenses 371 | RemoteAttestation RemoteAttestation = 5; 372 | } 373 | 374 | message SignedLicense { 375 | enum MessageType { 376 | DUMMY_MSG_TYPE = 0; // dummy, added to satisfy proto3 377 | LICENSE_REQUEST = 1; 378 | LICENSE = 2; 379 | ERROR_RESPONSE = 3; 380 | SERVICE_CERTIFICATE_REQUEST = 4; 381 | SERVICE_CERTIFICATE = 5; 382 | } 383 | MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 384 | License Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 385 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 386 | bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 387 | bytes SessionKey = 4; // often RSA wrapped for licenses 388 | RemoteAttestation RemoteAttestation = 5; 389 | } -------------------------------------------------------------------------------- /cdm/cdm.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | import os 4 | import time 5 | import binascii 6 | import base64 7 | from google.protobuf.message import DecodeError 8 | from google.protobuf import text_format 9 | import hashlib 10 | from cdm.formats import wv_proto2_pb2 as wv_proto2 11 | from cdm.session import Session 12 | from cdm.key import Key 13 | from Cryptodome.Random import get_random_bytes 14 | from Cryptodome.Random import random 15 | from Cryptodome.Cipher import PKCS1_OAEP, AES 16 | from Cryptodome.Hash import CMAC, SHA256, HMAC, SHA1 17 | from Cryptodome.PublicKey import RSA 18 | from Cryptodome.Signature import pss 19 | from Cryptodome.Util import Padding 20 | import logging 21 | 22 | class Cdm: 23 | def __init__(self): 24 | self.logger = logging.getLogger(__name__) 25 | self.sessions = {} 26 | 27 | def open_session(self, init_data_b64, device, raw_init_data = None, offline=False): 28 | self.logger.debug("open_session(init_data_b64={}, device={}".format(init_data_b64, device)) 29 | self.logger.info("opening new cdm session") 30 | if device.session_id_type == 'android': 31 | # format: 16 random hexdigits, 2 digit counter, 14 0s 32 | rand_ascii = ''.join(random.choice('ABCDEF0123456789') for _ in range(16)) 33 | counter = '01' # this resets regularly so its fine to use 01 34 | rest = '00000000000000' 35 | session_id = rand_ascii + counter + rest 36 | session_id = session_id.encode('ascii') 37 | elif device.session_id_type == 'chrome': 38 | rand_bytes = get_random_bytes(16) 39 | session_id = rand_bytes 40 | else: 41 | # other formats NYI 42 | self.logger.error("device type is unusable") 43 | return 1 44 | if raw_init_data and isinstance(raw_init_data, (bytes, bytearray)): 45 | # used for NF key exchange, where they don't provide a valid PSSH 46 | init_data = raw_init_data 47 | self.raw_pssh = True 48 | else: 49 | init_data = self._parse_init_data(init_data_b64) 50 | self.raw_pssh = False 51 | 52 | if init_data: 53 | new_session = Session(session_id, init_data, device, offline) 54 | else: 55 | self.logger.error("unable to parse init data") 56 | return 1 57 | self.sessions[session_id] = new_session 58 | self.logger.info("session opened and init data parsed successfully") 59 | return session_id 60 | 61 | def _parse_init_data(self, init_data_b64): 62 | parsed_init_data = wv_proto2.WidevineCencHeader() 63 | try: 64 | self.logger.debug("trying to parse init_data directly") 65 | parsed_init_data.ParseFromString(base64.b64decode(init_data_b64)[32:]) 66 | except DecodeError: 67 | self.logger.debug("unable to parse as-is, trying with removed pssh box header") 68 | try: 69 | id_bytes = parsed_init_data.ParseFromString(base64.b64decode(init_data_b64)[32:]) 70 | except DecodeError: 71 | self.logger.error("unable to parse, unsupported init data format") 72 | return None 73 | self.logger.debug("init_data:") 74 | for line in text_format.MessageToString(parsed_init_data).splitlines(): 75 | self.logger.debug(line) 76 | return parsed_init_data 77 | 78 | def close_session(self, session_id): 79 | self.logger.debug("close_session(session_id={})".format(session_id)) 80 | self.logger.info("closing cdm session") 81 | if session_id in self.sessions: 82 | self.sessions.pop(session_id) 83 | self.logger.info("cdm session closed") 84 | return 0 85 | else: 86 | self.logger.info("session {} not found".format(session_id)) 87 | return 1 88 | 89 | def set_service_certificate(self, session_id, cert_b64): 90 | self.logger.debug("set_service_certificate(session_id={}, cert={})".format(session_id, cert_b64)) 91 | self.logger.info("setting service certificate") 92 | 93 | if session_id not in self.sessions: 94 | self.logger.error("session id doesn't exist") 95 | return 1 96 | 97 | session = self.sessions[session_id] 98 | 99 | message = wv_proto2.SignedMessage() 100 | 101 | try: 102 | message.ParseFromString(base64.b64decode(cert_b64)) 103 | except DecodeError: 104 | self.logger.error("failed to parse cert as SignedMessage") 105 | 106 | service_certificate = wv_proto2.SignedDeviceCertificate() 107 | 108 | if message.Type: 109 | self.logger.debug("service cert provided as signedmessage") 110 | try: 111 | service_certificate.ParseFromString(message.Msg) 112 | except DecodeError: 113 | self.logger.error("failed to parse service certificate") 114 | return 1 115 | else: 116 | self.logger.debug("service cert provided as signeddevicecertificate") 117 | try: 118 | service_certificate.ParseFromString(base64.b64decode(cert_b64)) 119 | except DecodeError: 120 | self.logger.error("failed to parse service certificate") 121 | return 1 122 | 123 | self.logger.debug("service certificate:") 124 | for line in text_format.MessageToString(service_certificate).splitlines(): 125 | self.logger.debug(line) 126 | 127 | session.service_certificate = service_certificate 128 | session.privacy_mode = True 129 | 130 | return 0 131 | 132 | def get_license_request(self, session_id): 133 | self.logger.debug("get_license_request(session_id={})".format(session_id)) 134 | self.logger.info("getting license request") 135 | 136 | if session_id not in self.sessions: 137 | self.logger.error("session ID does not exist") 138 | return 1 139 | 140 | session = self.sessions[session_id] 141 | 142 | # raw pssh will be treated as bytes and not parsed 143 | if self.raw_pssh: 144 | license_request = wv_proto2.SignedLicenseRequestRaw() 145 | else: 146 | license_request = wv_proto2.SignedLicenseRequest() 147 | client_id = wv_proto2.ClientIdentification() 148 | 149 | if not os.path.exists(session.device_config.device_client_id_blob_filename): 150 | self.logger.error("no client ID blob available for this device") 151 | return 1 152 | 153 | with open(session.device_config.device_client_id_blob_filename, "rb") as f: 154 | try: 155 | cid_bytes = client_id.ParseFromString(f.read()) 156 | except DecodeError: 157 | self.logger.error("client id failed to parse as protobuf") 158 | return 1 159 | 160 | self.logger.debug("building license request") 161 | if not self.raw_pssh: 162 | license_request.Type = wv_proto2.SignedLicenseRequest.MessageType.Value('LICENSE_REQUEST') 163 | license_request.Msg.ContentId.CencId.Pssh.CopyFrom(session.init_data) 164 | else: 165 | license_request.Type = wv_proto2.SignedLicenseRequestRaw.MessageType.Value('LICENSE_REQUEST') 166 | license_request.Msg.ContentId.CencId.Pssh = session.init_data # bytes 167 | 168 | if session.offline: 169 | license_type = wv_proto2.LicenseType.Value('OFFLINE') 170 | else: 171 | license_type = wv_proto2.LicenseType.Value('DEFAULT') 172 | license_request.Msg.ContentId.CencId.LicenseType = license_type 173 | license_request.Msg.ContentId.CencId.RequestId = session_id 174 | license_request.Msg.Type = wv_proto2.LicenseRequest.RequestType.Value('NEW') 175 | license_request.Msg.RequestTime = int(time.time()) 176 | license_request.Msg.ProtocolVersion = wv_proto2.ProtocolVersion.Value('CURRENT') 177 | if session.device_config.send_key_control_nonce: 178 | license_request.Msg.KeyControlNonce = random.randrange(1, 2**31) 179 | 180 | # wv_proto2.WidevineCencHeader().provider = 'conax' 181 | 182 | if session.privacy_mode: 183 | if session.device_config.vmp: 184 | self.logger.debug("vmp required, adding to client_id") 185 | self.logger.debug("reading vmp hashes") 186 | vmp_hashes = wv_proto2.FileHashes() 187 | with open(session.device_config.device_vmp_blob_filename, "rb") as f: 188 | try: 189 | vmp_bytes = vmp_hashes.ParseFromString(f.read()) 190 | except DecodeError: 191 | self.logger.error("vmp hashes failed to parse as protobuf") 192 | return 1 193 | client_id._FileHashes.CopyFrom(vmp_hashes) 194 | self.logger.debug("privacy mode & service certificate loaded, encrypting client id") 195 | self.logger.debug("unencrypted client id:") 196 | for line in text_format.MessageToString(client_id).splitlines(): 197 | self.logger.debug(line) 198 | cid_aes_key = get_random_bytes(16) 199 | cid_iv = get_random_bytes(16) 200 | 201 | cid_cipher = AES.new(cid_aes_key, AES.MODE_CBC, cid_iv) 202 | 203 | encrypted_client_id = cid_cipher.encrypt(Padding.pad(client_id.SerializeToString(), 16)) 204 | 205 | service_public_key = RSA.importKey(session.service_certificate._DeviceCertificate.PublicKey) 206 | 207 | service_cipher = PKCS1_OAEP.new(service_public_key) 208 | 209 | encrypted_cid_key = service_cipher.encrypt(cid_aes_key) 210 | 211 | encrypted_client_id_proto = wv_proto2.EncryptedClientIdentification() 212 | 213 | encrypted_client_id_proto.ServiceId = session.service_certificate._DeviceCertificate.ServiceId 214 | encrypted_client_id_proto.ServiceCertificateSerialNumber = session.service_certificate._DeviceCertificate.SerialNumber 215 | encrypted_client_id_proto.EncryptedClientId = encrypted_client_id 216 | encrypted_client_id_proto.EncryptedClientIdIv = cid_iv 217 | encrypted_client_id_proto.EncryptedPrivacyKey = encrypted_cid_key 218 | 219 | license_request.Msg.EncryptedClientId.CopyFrom(encrypted_client_id_proto) 220 | else: 221 | license_request.Msg.ClientId.CopyFrom(client_id) 222 | 223 | if session.device_config.private_key_available: 224 | key = RSA.importKey(open(session.device_config.device_private_key_filename).read()) 225 | session.device_key = key 226 | else: 227 | self.logger.error("need device private key, other methods unimplemented") 228 | return 1 229 | 230 | self.logger.debug("signing license request") 231 | 232 | hash = SHA1.new(license_request.Msg.SerializeToString()) 233 | 234 | signature = pss.new(key).sign(hash) 235 | 236 | global hash_object 237 | global hash2test 238 | m = hashlib.sha1() 239 | m.update(signature) 240 | # hash2test = m.digest() 241 | hash_object = m.hexdigest() 242 | 243 | license_request.Signature = signature 244 | 245 | session.license_request = license_request 246 | 247 | self.logger.debug("license request:") 248 | for line in text_format.MessageToString(session.license_request).splitlines(): 249 | self.logger.debug(line) 250 | self.logger.info("license request created") 251 | self.logger.debug("license request b64: {}".format(base64.b64encode(license_request.SerializeToString()))) 252 | return license_request.SerializeToString() 253 | 254 | def provide_license(self, session_id, license_b64): 255 | self.logger.debug("provide_license(session_id={}, license_b64={})".format(session_id, license_b64)) 256 | self.logger.info("decrypting provided license") 257 | 258 | if session_id not in self.sessions: 259 | self.logger.error("session does not exist") 260 | return 1 261 | 262 | session = self.sessions[session_id] 263 | 264 | if not session.license_request: 265 | self.logger.error("generate a license request first!") 266 | return 1 267 | 268 | license = wv_proto2.SignedLicense() 269 | try: 270 | license.ParseFromString(base64.b64decode(license_b64)) 271 | except DecodeError: 272 | self.logger.error("unable to parse license - check protobufs") 273 | return 1 274 | 275 | session.license = license 276 | 277 | self.logger.debug("license:") 278 | for line in text_format.MessageToString(license).splitlines(): 279 | self.logger.debug(line) 280 | 281 | self.logger.debug("deriving keys from session key") 282 | 283 | oaep_cipher = PKCS1_OAEP.new(session.device_key) 284 | 285 | session.session_key = oaep_cipher.decrypt(license.SessionKey) 286 | 287 | lic_req_msg = session.license_request.Msg.SerializeToString() 288 | 289 | enc_key_base = b"ENCRYPTION\000" + lic_req_msg + b"\0\0\0\x80" 290 | auth_key_base = b"AUTHENTICATION\0" + lic_req_msg + b"\0\0\2\0" 291 | 292 | enc_key = b"\x01" + enc_key_base 293 | auth_key_1 = b"\x01" + auth_key_base 294 | auth_key_2 = b"\x02" + auth_key_base 295 | auth_key_3 = b"\x03" + auth_key_base 296 | auth_key_4 = b"\x04" + auth_key_base 297 | 298 | cmac_obj = CMAC.new(session.session_key, ciphermod=AES) 299 | cmac_obj.update(enc_key) 300 | 301 | enc_cmac_key = cmac_obj.digest() 302 | 303 | cmac_obj = CMAC.new(session.session_key, ciphermod=AES) 304 | cmac_obj.update(auth_key_1) 305 | auth_cmac_key_1 = cmac_obj.digest() 306 | 307 | cmac_obj = CMAC.new(session.session_key, ciphermod=AES) 308 | cmac_obj.update(auth_key_2) 309 | auth_cmac_key_2 = cmac_obj.digest() 310 | 311 | cmac_obj = CMAC.new(session.session_key, ciphermod=AES) 312 | cmac_obj.update(auth_key_3) 313 | auth_cmac_key_3 = cmac_obj.digest() 314 | 315 | cmac_obj = CMAC.new(session.session_key, ciphermod=AES) 316 | cmac_obj.update(auth_key_4) 317 | auth_cmac_key_4 = cmac_obj.digest() 318 | 319 | auth_cmac_combined_1 = auth_cmac_key_1 + auth_cmac_key_2 320 | auth_cmac_combined_2 = auth_cmac_key_3 + auth_cmac_key_4 321 | 322 | session.derived_keys['enc'] = enc_cmac_key 323 | session.derived_keys['auth_1'] = auth_cmac_combined_1 324 | session.derived_keys['auth_2'] = auth_cmac_combined_2 325 | 326 | self.logger.debug('verifying license signature') 327 | 328 | lic_hmac = HMAC.new(session.derived_keys['auth_1'], digestmod=SHA256) 329 | lic_hmac.update(license.Msg.SerializeToString()) 330 | 331 | self.logger.debug("calculated sig: {} actual sig: {}".format(lic_hmac.hexdigest(), binascii.hexlify(license.Signature))) 332 | 333 | if lic_hmac.digest() != license.Signature: 334 | self.logger.info("license signature doesn't match - writing bin so they can be debugged") 335 | with open("original_lic.bin", "wb") as f: 336 | f.write(base64.b64decode(license_b64)) 337 | with open("parsed_lic.bin", "wb") as f: 338 | f.write(license.SerializeToString()) 339 | self.logger.info("continuing anyway") 340 | 341 | self.logger.debug("key count: {}".format(len(license.Msg.Key))) 342 | for key in license.Msg.Key: 343 | if key.Id: 344 | key_id = key.Id 345 | else: 346 | key_id = wv_proto2.License.KeyContainer.KeyType.Name(key.Type).encode('utf-8') 347 | encrypted_key = key.Key 348 | iv = key.Iv 349 | type = wv_proto2.License.KeyContainer.KeyType.Name(key.Type) 350 | 351 | cipher = AES.new(session.derived_keys['enc'], AES.MODE_CBC, iv=iv) 352 | decrypted_key = cipher.decrypt(encrypted_key) 353 | if type == "OPERATOR_SESSION": 354 | permissions = [] 355 | perms = key._OperatorSessionKeyPermissions 356 | for (descriptor, value) in perms.ListFields(): 357 | if value == 1: 358 | permissions.append(descriptor.name) 359 | print(permissions) 360 | else: 361 | permissions = [] 362 | session.keys.append(Key(key_id, type, Padding.unpad(decrypted_key, 16), permissions)) 363 | 364 | self.logger.info("decrypted all keys") 365 | return 0 366 | 367 | def get_keys(self, session_id): 368 | if session_id in self.sessions: 369 | return self.sessions[session_id].keys 370 | else: 371 | self.logger.error("session not found") 372 | return 1 373 | -------------------------------------------------------------------------------- /cdm/formats/wv_proto2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | 4 | // This message is copied from google's docs, not reversed: 5 | message WidevineCencHeader { 6 | enum Algorithm { 7 | UNENCRYPTED = 0; 8 | AESCTR = 1; 9 | }; 10 | optional Algorithm algorithm = 1; 11 | repeated bytes key_id = 2; 12 | 13 | // Content provider name. 14 | optional string provider = 3; 15 | 16 | // A content identifier, specified by content provider. 17 | optional bytes content_id = 4; 18 | 19 | // Track type. Acceptable values are SD, HD and AUDIO. Used to 20 | // differentiate content keys used by an asset. 21 | optional string track_type_deprecated = 5; 22 | 23 | // The name of a registered policy to be used for this asset. 24 | optional string policy = 6; 25 | 26 | // Crypto period index, for media using key rotation. 27 | optional uint32 crypto_period_index = 7; 28 | 29 | // Optional protected context for group content. The grouped_license is a 30 | // serialized SignedMessage. 31 | optional bytes grouped_license = 8; 32 | 33 | // Protection scheme identifying the encryption algorithm. 34 | // Represented as one of the following 4CC values: 35 | // 'cenc' (AESCTR), 'cbc1' (AESCBC), 36 | // 'cens' (AESCTR subsample), 'cbcs' (AESCBC subsample). 37 | optional uint32 protection_scheme = 9; 38 | 39 | // Optional. For media using key rotation, this represents the duration 40 | // of each crypto period in seconds. 41 | optional uint32 crypto_period_seconds = 10; 42 | } 43 | 44 | 45 | 46 | // from x86 (partial), most of it from the ARM version: 47 | message ClientIdentification { 48 | enum TokenType { 49 | KEYBOX = 0; 50 | DEVICE_CERTIFICATE = 1; 51 | REMOTE_ATTESTATION_CERTIFICATE = 2; 52 | } 53 | message NameValue { 54 | required string Name = 1; 55 | required string Value = 2; 56 | } 57 | message ClientCapabilities { 58 | enum HdcpVersion { 59 | HDCP_NONE = 0; 60 | HDCP_V1 = 1; 61 | HDCP_V2 = 2; 62 | HDCP_V2_1 = 3; 63 | HDCP_V2_2 = 4; 64 | } 65 | optional uint32 ClientToken = 1; 66 | optional uint32 SessionToken = 2; 67 | optional uint32 VideoResolutionConstraints = 3; 68 | optional HdcpVersion MaxHdcpVersion = 4; 69 | optional uint32 OemCryptoApiVersion = 5; 70 | } 71 | required TokenType Type = 1; 72 | //optional bytes Token = 2; // by default the client treats this as blob, but it's usually a DeviceCertificate, so for usefulness sake, I'm replacing it with this one: 73 | optional SignedDeviceCertificate Token = 2; // use this when parsing, "bytes" when building a client id blob 74 | repeated NameValue ClientInfo = 3; 75 | optional bytes ProviderClientToken = 4; 76 | optional uint32 LicenseCounter = 5; 77 | optional ClientCapabilities _ClientCapabilities = 6; // how should we deal with duped names? will have to look at proto docs later 78 | optional FileHashes _FileHashes = 7; // vmp blob goes here 79 | } 80 | 81 | message DeviceCertificate { 82 | enum CertificateType { 83 | ROOT = 0; 84 | INTERMEDIATE = 1; 85 | USER_DEVICE = 2; 86 | SERVICE = 3; 87 | } 88 | required CertificateType Type = 1; // the compiled code reused this as ProvisionedDeviceInfo.WvSecurityLevel, however that is incorrect (compiler aliased it as they're both identical as a structure) 89 | optional bytes SerialNumber = 2; 90 | optional uint32 CreationTimeSeconds = 3; 91 | optional bytes PublicKey = 4; 92 | optional uint32 SystemId = 5; 93 | optional uint32 TestDeviceDeprecated = 6; // is it bool or int? 94 | optional bytes ServiceId = 7; // service URL for service certificates 95 | } 96 | 97 | // missing some references, 98 | message DeviceCertificateStatus { 99 | enum CertificateStatus { 100 | VALID = 0; 101 | REVOKED = 1; 102 | } 103 | optional bytes SerialNumber = 1; 104 | optional CertificateStatus Status = 2; 105 | optional ProvisionedDeviceInfo DeviceInfo = 4; // where is 3? is it deprecated? 106 | } 107 | 108 | message DeviceCertificateStatusList { 109 | optional uint32 CreationTimeSeconds = 1; 110 | repeated DeviceCertificateStatus CertificateStatus = 2; 111 | } 112 | 113 | message EncryptedClientIdentification { 114 | required string ServiceId = 1; 115 | optional bytes ServiceCertificateSerialNumber = 2; 116 | required bytes EncryptedClientId = 3; 117 | required bytes EncryptedClientIdIv = 4; 118 | required bytes EncryptedPrivacyKey = 5; 119 | } 120 | 121 | // todo: fill (for this top-level type, it might be impossible/difficult) 122 | enum LicenseType { 123 | ZERO = 0; 124 | DEFAULT = 1; // 1 is STREAMING/temporary license; on recent versions may go up to 3 (latest x86); it might be persist/don't persist type, unconfirmed 125 | OFFLINE = 2; 126 | } 127 | 128 | // todo: fill (for this top-level type, it might be impossible/difficult) 129 | // this is just a guess because these globals got lost, but really, do we need more? 130 | enum ProtocolVersion { 131 | CURRENT = 21; // don't have symbols for this 132 | } 133 | 134 | 135 | message LicenseIdentification { 136 | optional bytes RequestId = 1; 137 | optional bytes SessionId = 2; 138 | optional bytes PurchaseId = 3; 139 | optional LicenseType Type = 4; 140 | optional uint32 Version = 5; 141 | optional bytes ProviderSessionToken = 6; 142 | } 143 | 144 | 145 | message License { 146 | message Policy { 147 | optional bool CanPlay = 1; // changed from uint32 to bool 148 | optional bool CanPersist = 2; 149 | optional bool CanRenew = 3; 150 | optional uint32 RentalDurationSeconds = 4; 151 | optional uint32 PlaybackDurationSeconds = 5; 152 | optional uint32 LicenseDurationSeconds = 6; 153 | optional uint32 RenewalRecoveryDurationSeconds = 7; 154 | optional string RenewalServerUrl = 8; 155 | optional uint32 RenewalDelaySeconds = 9; 156 | optional uint32 RenewalRetryIntervalSeconds = 10; 157 | optional bool RenewWithUsage = 11; // was uint32 158 | } 159 | message KeyContainer { 160 | enum KeyType { 161 | SIGNING = 1; 162 | CONTENT = 2; 163 | KEY_CONTROL = 3; 164 | OPERATOR_SESSION = 4; 165 | } 166 | enum SecurityLevel { 167 | SW_SECURE_CRYPTO = 1; 168 | SW_SECURE_DECODE = 2; 169 | HW_SECURE_CRYPTO = 3; 170 | HW_SECURE_DECODE = 4; 171 | HW_SECURE_ALL = 5; 172 | } 173 | message OutputProtection { 174 | enum CGMS { 175 | COPY_FREE = 0; 176 | COPY_ONCE = 2; 177 | COPY_NEVER = 3; 178 | CGMS_NONE = 0x2A; // PC default! 179 | } 180 | optional ClientIdentification.ClientCapabilities.HdcpVersion Hdcp = 1; // it's most likely a copy of Hdcp version available here, but compiler optimized it away 181 | optional CGMS CgmsFlags = 2; 182 | } 183 | message KeyControl { 184 | required bytes KeyControlBlock = 1; // what is this? 185 | required bytes Iv = 2; 186 | } 187 | message OperatorSessionKeyPermissions { 188 | optional uint32 AllowEncrypt = 1; 189 | optional uint32 AllowDecrypt = 2; 190 | optional uint32 AllowSign = 3; 191 | optional uint32 AllowSignatureVerify = 4; 192 | } 193 | message VideoResolutionConstraint { 194 | optional uint32 MinResolutionPixels = 1; 195 | optional uint32 MaxResolutionPixels = 2; 196 | optional OutputProtection RequiredProtection = 3; 197 | } 198 | optional bytes Id = 1; 199 | optional bytes Iv = 2; 200 | optional bytes Key = 3; 201 | optional KeyType Type = 4; 202 | optional SecurityLevel Level = 5; 203 | optional OutputProtection RequiredProtection = 6; 204 | optional OutputProtection RequestedProtection = 7; 205 | optional KeyControl _KeyControl = 8; // duped names, etc 206 | optional OperatorSessionKeyPermissions _OperatorSessionKeyPermissions = 9; // duped names, etc 207 | repeated VideoResolutionConstraint VideoResolutionConstraints = 10; 208 | } 209 | optional LicenseIdentification Id = 1; 210 | optional Policy _Policy = 2; // duped names, etc 211 | repeated KeyContainer Key = 3; 212 | optional uint32 LicenseStartTime = 4; 213 | optional uint32 RemoteAttestationVerified = 5; // bool? 214 | optional bytes ProviderClientToken = 6; 215 | // there might be more, check with newer versions (I see field 7-8 in a lic) 216 | // this appeared in latest x86: 217 | optional uint32 ProtectionScheme = 7; // type unconfirmed fully, but it's likely as WidevineCencHeader describesit (fourcc) 218 | } 219 | 220 | message LicenseError { 221 | enum Error { 222 | INVALID_DEVICE_CERTIFICATE = 1; 223 | REVOKED_DEVICE_CERTIFICATE = 2; 224 | SERVICE_UNAVAILABLE = 3; 225 | } 226 | //LicenseRequest.RequestType ErrorCode; // clang mismatch 227 | optional Error ErrorCode = 1; 228 | } 229 | 230 | message LicenseRequest { 231 | message ContentIdentification { 232 | message CENC { 233 | //optional bytes Pssh = 1; // the client's definition is opaque, it doesn't care about the contents, but the PSSH has a clear definition that is understood and requested by the server, thus I'll replace it with: 234 | optional WidevineCencHeader Pssh = 1; 235 | optional LicenseType LicenseType = 2; // unfortunately the LicenseType symbols are not present, acceptable value seems to only be 1 (is this persist/don't persist? look into it!) 236 | optional bytes RequestId = 3; 237 | } 238 | message WebM { 239 | optional bytes Header = 1; // identical to CENC, aside from PSSH and the parent field number used 240 | optional LicenseType LicenseType = 2; 241 | optional bytes RequestId = 3; 242 | } 243 | message ExistingLicense { 244 | optional LicenseIdentification LicenseId = 1; 245 | optional uint32 SecondsSinceStarted = 2; 246 | optional uint32 SecondsSinceLastPlayed = 3; 247 | optional bytes SessionUsageTableEntry = 4; // interesting! try to figure out the connection between the usage table blob and KCB! 248 | } 249 | optional CENC CencId = 1; 250 | optional WebM WebmId = 2; 251 | optional ExistingLicense License = 3; 252 | } 253 | enum RequestType { 254 | NEW = 1; 255 | RENEWAL = 2; 256 | RELEASE = 3; 257 | } 258 | optional ClientIdentification ClientId = 1; 259 | optional ContentIdentification ContentId = 2; 260 | optional RequestType Type = 3; 261 | optional uint32 RequestTime = 4; 262 | optional bytes KeyControlNonceDeprecated = 5; 263 | optional ProtocolVersion ProtocolVersion = 6; // lacking symbols for this 264 | optional uint32 KeyControlNonce = 7; 265 | optional EncryptedClientIdentification EncryptedClientId = 8; 266 | optional string version = 9; 267 | optional string puid = 10; 268 | optional string watchSessionId = 11; 269 | optional string contentId = 12; 270 | optional string contentTypeId = 13; 271 | optional string serviceName = 14; 272 | optional string productId = 15; 273 | optional string monetizationModel = 16; 274 | optional string expirationTimestamp = 17; 275 | optional string verificationRequired = 18; 276 | optional string signature = 19; 277 | } 278 | 279 | // raw pssh hack 280 | message LicenseRequestRaw { 281 | message ContentIdentification { 282 | message CENC { 283 | optional bytes Pssh = 1; // the client's definition is opaque, it doesn't care about the contents, but the PSSH has a clear definition that is understood and requested by the server, thus I'll replace it with: 284 | //optional WidevineCencHeader Pssh = 1; 285 | optional LicenseType LicenseType = 2; // unfortunately the LicenseType symbols are not present, acceptable value seems to only be 1 (is this persist/don't persist? look into it!) 286 | optional bytes RequestId = 3; 287 | } 288 | message WebM { 289 | optional bytes Header = 1; // identical to CENC, aside from PSSH and the parent field number used 290 | optional LicenseType LicenseType = 2; 291 | optional bytes RequestId = 3; 292 | } 293 | message ExistingLicense { 294 | optional LicenseIdentification LicenseId = 1; 295 | optional uint32 SecondsSinceStarted = 2; 296 | optional uint32 SecondsSinceLastPlayed = 3; 297 | optional bytes SessionUsageTableEntry = 4; // interesting! try to figure out the connection between the usage table blob and KCB! 298 | } 299 | optional CENC CencId = 1; 300 | optional WebM WebmId = 2; 301 | optional ExistingLicense License = 3; 302 | } 303 | enum RequestType { 304 | NEW = 1; 305 | RENEWAL = 2; 306 | RELEASE = 3; 307 | } 308 | optional ClientIdentification ClientId = 1; 309 | optional ContentIdentification ContentId = 2; 310 | optional RequestType Type = 3; 311 | optional uint32 RequestTime = 4; 312 | optional bytes KeyControlNonceDeprecated = 5; 313 | optional ProtocolVersion ProtocolVersion = 6; // lacking symbols for this 314 | optional uint32 KeyControlNonce = 7; 315 | optional EncryptedClientIdentification EncryptedClientId = 8; 316 | } 317 | 318 | 319 | message ProvisionedDeviceInfo { 320 | enum WvSecurityLevel { 321 | LEVEL_UNSPECIFIED = 0; 322 | LEVEL_1 = 1; 323 | LEVEL_2 = 2; 324 | LEVEL_3 = 3; 325 | } 326 | optional uint32 SystemId = 1; 327 | optional string Soc = 2; 328 | optional string Manufacturer = 3; 329 | optional string Model = 4; 330 | optional string DeviceType = 5; 331 | optional uint32 ModelYear = 6; 332 | optional WvSecurityLevel SecurityLevel = 7; 333 | optional uint32 TestDevice = 8; // bool? 334 | } 335 | 336 | 337 | // todo: fill 338 | message ProvisioningOptions { 339 | } 340 | 341 | // todo: fill 342 | message ProvisioningRequest { 343 | } 344 | 345 | // todo: fill 346 | message ProvisioningResponse { 347 | } 348 | 349 | message RemoteAttestation { 350 | optional EncryptedClientIdentification Certificate = 1; 351 | optional string Salt = 2; 352 | optional string Signature = 3; 353 | } 354 | 355 | // todo: fill 356 | message SessionInit { 357 | } 358 | 359 | // todo: fill 360 | message SessionState { 361 | } 362 | 363 | // todo: fill 364 | message SignedCertificateStatusList { 365 | } 366 | 367 | message SignedDeviceCertificate { 368 | 369 | //optional bytes DeviceCertificate = 1; // again, they use a buffer where it's supposed to be a message, so we'll replace it with what it really is: 370 | optional DeviceCertificate _DeviceCertificate = 1; // how should we deal with duped names? will have to look at proto docs later 371 | optional bytes Signature = 2; 372 | optional SignedDeviceCertificate Signer = 3; 373 | } 374 | 375 | 376 | // todo: fill 377 | message SignedProvisioningMessage { 378 | } 379 | 380 | // the root of all messages, from either server or client 381 | message SignedMessage { 382 | enum MessageType { 383 | LICENSE_REQUEST = 1; 384 | LICENSE = 2; 385 | ERROR_RESPONSE = 3; 386 | SERVICE_CERTIFICATE_REQUEST = 4; 387 | SERVICE_CERTIFICATE = 5; 388 | } 389 | optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 390 | optional bytes Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 391 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 392 | optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 393 | optional bytes SessionKey = 4; // often RSA wrapped for licenses 394 | optional RemoteAttestation RemoteAttestation = 5; 395 | } 396 | 397 | 398 | 399 | // remove these when using it outside of protoc: 400 | 401 | // from here on, it's just for testing, these messages don't exist in the binaries, I'm adding them to avoid detecting type programmatically 402 | message SignedLicenseRequest { 403 | enum MessageType { 404 | LICENSE_REQUEST = 1; 405 | LICENSE = 2; 406 | ERROR_RESPONSE = 3; 407 | SERVICE_CERTIFICATE_REQUEST = 4; 408 | SERVICE_CERTIFICATE = 5; 409 | } 410 | optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 411 | optional LicenseRequest Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 412 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 413 | optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 414 | optional bytes SessionKey = 4; // often RSA wrapped for licenses 415 | optional RemoteAttestation RemoteAttestation = 5; 416 | } 417 | 418 | // hack 419 | message SignedLicenseRequestRaw { 420 | enum MessageType { 421 | LICENSE_REQUEST = 1; 422 | LICENSE = 2; 423 | ERROR_RESPONSE = 3; 424 | SERVICE_CERTIFICATE_REQUEST = 4; 425 | SERVICE_CERTIFICATE = 5; 426 | } 427 | optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 428 | optional LicenseRequestRaw Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 429 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 430 | optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 431 | optional bytes SessionKey = 4; // often RSA wrapped for licenses 432 | optional RemoteAttestation RemoteAttestation = 5; 433 | } 434 | 435 | 436 | message SignedLicense { 437 | enum MessageType { 438 | LICENSE_REQUEST = 1; 439 | LICENSE = 2; 440 | ERROR_RESPONSE = 3; 441 | SERVICE_CERTIFICATE_REQUEST = 4; 442 | SERVICE_CERTIFICATE = 5; 443 | } 444 | optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 445 | optional License Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 446 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 447 | optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 448 | optional bytes SessionKey = 4; // often RSA wrapped for licenses 449 | optional RemoteAttestation RemoteAttestation = 5; 450 | } 451 | 452 | message SignedServiceCertificate { 453 | enum MessageType { 454 | LICENSE_REQUEST = 1; 455 | LICENSE = 2; 456 | ERROR_RESPONSE = 3; 457 | SERVICE_CERTIFICATE_REQUEST = 4; 458 | SERVICE_CERTIFICATE = 5; 459 | } 460 | optional MessageType Type = 1; // has in incorrect overlap with License_KeyContainer_SecurityLevel 461 | optional SignedDeviceCertificate Msg = 2; // this has to be casted dynamically, to LicenseRequest, License or LicenseError (? unconfirmed), for Request, no other fields but Type need to be present 462 | // for SERVICE_CERTIFICATE, only Type and Msg are present, and it's just a DeviceCertificate with CertificateType set to SERVICE 463 | optional bytes Signature = 3; // might be different type of signatures (ex. RSA vs AES CMAC(??), unconfirmed for now) 464 | optional bytes SessionKey = 4; // often RSA wrapped for licenses 465 | optional RemoteAttestation RemoteAttestation = 5; 466 | } 467 | 468 | //vmp support 469 | message FileHashes { 470 | message Signature { 471 | optional string filename = 1; 472 | optional bool test_signing = 2; //0 - release, 1 - testing 473 | optional bytes SHA512Hash = 3; 474 | optional bool main_exe = 4; //0 for dlls, 1 for exe, this is field 3 in file 475 | optional bytes signature = 5; 476 | } 477 | optional bytes signer = 1; 478 | repeated Signature signatures = 2; 479 | } 480 | 481 | -------------------------------------------------------------------------------- /proto_compiler/include/google/protobuf/descriptor.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | // Author: kenton@google.com (Kenton Varda) 32 | // Based on original Protocol Buffers design by 33 | // Sanjay Ghemawat, Jeff Dean, and others. 34 | // 35 | // The messages in this file describe the definitions found in .proto files. 36 | // A valid .proto file can be translated directly to a FileDescriptorProto 37 | // without any other information (e.g. without reading its imports). 38 | 39 | 40 | syntax = "proto2"; 41 | 42 | package google.protobuf; 43 | 44 | option go_package = "google.golang.org/protobuf/types/descriptorpb"; 45 | option java_package = "com.google.protobuf"; 46 | option java_outer_classname = "DescriptorProtos"; 47 | option csharp_namespace = "Google.Protobuf.Reflection"; 48 | option objc_class_prefix = "GPB"; 49 | option cc_enable_arenas = true; 50 | 51 | // descriptor.proto must be optimized for speed because reflection-based 52 | // algorithms don't work during bootstrapping. 53 | option optimize_for = SPEED; 54 | 55 | // The protocol compiler can output a FileDescriptorSet containing the .proto 56 | // files it parses. 57 | message FileDescriptorSet { 58 | repeated FileDescriptorProto file = 1; 59 | } 60 | 61 | // Describes a complete .proto file. 62 | message FileDescriptorProto { 63 | optional string name = 1; // file name, relative to root of source tree 64 | optional string package = 2; // e.g. "foo", "foo.bar", etc. 65 | 66 | // Names of files imported by this file. 67 | repeated string dependency = 3; 68 | // Indexes of the public imported files in the dependency list above. 69 | repeated int32 public_dependency = 10; 70 | // Indexes of the weak imported files in the dependency list. 71 | // For Google-internal migration only. Do not use. 72 | repeated int32 weak_dependency = 11; 73 | 74 | // All top-level definitions in this file. 75 | repeated DescriptorProto message_type = 4; 76 | repeated EnumDescriptorProto enum_type = 5; 77 | repeated ServiceDescriptorProto service = 6; 78 | repeated FieldDescriptorProto extension = 7; 79 | 80 | optional FileOptions options = 8; 81 | 82 | // This field contains optional information about the original source code. 83 | // You may safely remove this entire field without harming runtime 84 | // functionality of the descriptors -- the information is needed only by 85 | // development tools. 86 | optional SourceCodeInfo source_code_info = 9; 87 | 88 | // The syntax of the proto file. 89 | // The supported values are "proto2" and "proto3". 90 | optional string syntax = 12; 91 | } 92 | 93 | // Describes a message type. 94 | message DescriptorProto { 95 | optional string name = 1; 96 | 97 | repeated FieldDescriptorProto field = 2; 98 | repeated FieldDescriptorProto extension = 6; 99 | 100 | repeated DescriptorProto nested_type = 3; 101 | repeated EnumDescriptorProto enum_type = 4; 102 | 103 | message ExtensionRange { 104 | optional int32 start = 1; // Inclusive. 105 | optional int32 end = 2; // Exclusive. 106 | 107 | optional ExtensionRangeOptions options = 3; 108 | } 109 | repeated ExtensionRange extension_range = 5; 110 | 111 | repeated OneofDescriptorProto oneof_decl = 8; 112 | 113 | optional MessageOptions options = 7; 114 | 115 | // Range of reserved tag numbers. Reserved tag numbers may not be used by 116 | // fields or extension ranges in the same message. Reserved ranges may 117 | // not overlap. 118 | message ReservedRange { 119 | optional int32 start = 1; // Inclusive. 120 | optional int32 end = 2; // Exclusive. 121 | } 122 | repeated ReservedRange reserved_range = 9; 123 | // Reserved field names, which may not be used by fields in the same message. 124 | // A given name may only be reserved once. 125 | repeated string reserved_name = 10; 126 | } 127 | 128 | message ExtensionRangeOptions { 129 | // The parser stores options it doesn't recognize here. See above. 130 | repeated UninterpretedOption uninterpreted_option = 999; 131 | 132 | 133 | // Clients can define custom options in extensions of this message. See above. 134 | extensions 1000 to max; 135 | } 136 | 137 | // Describes a field within a message. 138 | message FieldDescriptorProto { 139 | enum Type { 140 | // 0 is reserved for errors. 141 | // Order is weird for historical reasons. 142 | TYPE_DOUBLE = 1; 143 | TYPE_FLOAT = 2; 144 | // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if 145 | // negative values are likely. 146 | TYPE_INT64 = 3; 147 | TYPE_UINT64 = 4; 148 | // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if 149 | // negative values are likely. 150 | TYPE_INT32 = 5; 151 | TYPE_FIXED64 = 6; 152 | TYPE_FIXED32 = 7; 153 | TYPE_BOOL = 8; 154 | TYPE_STRING = 9; 155 | // Tag-delimited aggregate. 156 | // Group type is deprecated and not supported in proto3. However, Proto3 157 | // implementations should still be able to parse the group wire format and 158 | // treat group fields as unknown fields. 159 | TYPE_GROUP = 10; 160 | TYPE_MESSAGE = 11; // Length-delimited aggregate. 161 | 162 | // New in version 2. 163 | TYPE_BYTES = 12; 164 | TYPE_UINT32 = 13; 165 | TYPE_ENUM = 14; 166 | TYPE_SFIXED32 = 15; 167 | TYPE_SFIXED64 = 16; 168 | TYPE_SINT32 = 17; // Uses ZigZag encoding. 169 | TYPE_SINT64 = 18; // Uses ZigZag encoding. 170 | } 171 | 172 | enum Label { 173 | // 0 is reserved for errors 174 | LABEL_OPTIONAL = 1; 175 | LABEL_REQUIRED = 2; 176 | LABEL_REPEATED = 3; 177 | } 178 | 179 | optional string name = 1; 180 | optional int32 number = 3; 181 | optional Label label = 4; 182 | 183 | // If type_name is set, this need not be set. If both this and type_name 184 | // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. 185 | optional Type type = 5; 186 | 187 | // For message and enum types, this is the name of the type. If the name 188 | // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping 189 | // rules are used to find the type (i.e. first the nested types within this 190 | // message are searched, then within the parent, on up to the root 191 | // namespace). 192 | optional string type_name = 6; 193 | 194 | // For extensions, this is the name of the type being extended. It is 195 | // resolved in the same manner as type_name. 196 | optional string extendee = 2; 197 | 198 | // For numeric types, contains the original text representation of the value. 199 | // For booleans, "true" or "false". 200 | // For strings, contains the default text contents (not escaped in any way). 201 | // For bytes, contains the C escaped value. All bytes >= 128 are escaped. 202 | // TODO(kenton): Base-64 encode? 203 | optional string default_value = 7; 204 | 205 | // If set, gives the index of a oneof in the containing type's oneof_decl 206 | // list. This field is a member of that oneof. 207 | optional int32 oneof_index = 9; 208 | 209 | // JSON name of this field. The value is set by protocol compiler. If the 210 | // user has set a "json_name" option on this field, that option's value 211 | // will be used. Otherwise, it's deduced from the field's name by converting 212 | // it to camelCase. 213 | optional string json_name = 10; 214 | 215 | optional FieldOptions options = 8; 216 | 217 | // If true, this is a proto3 "optional". When a proto3 field is optional, it 218 | // tracks presence regardless of field type. 219 | // 220 | // When proto3_optional is true, this field must be belong to a oneof to 221 | // signal to old proto3 clients that presence is tracked for this field. This 222 | // oneof is known as a "synthetic" oneof, and this field must be its sole 223 | // member (each proto3 optional field gets its own synthetic oneof). Synthetic 224 | // oneofs exist in the descriptor only, and do not generate any API. Synthetic 225 | // oneofs must be ordered after all "real" oneofs. 226 | // 227 | // For message fields, proto3_optional doesn't create any semantic change, 228 | // since non-repeated message fields always track presence. However it still 229 | // indicates the semantic detail of whether the user wrote "optional" or not. 230 | // This can be useful for round-tripping the .proto file. For consistency we 231 | // give message fields a synthetic oneof also, even though it is not required 232 | // to track presence. This is especially important because the parser can't 233 | // tell if a field is a message or an enum, so it must always create a 234 | // synthetic oneof. 235 | // 236 | // Proto2 optional fields do not set this flag, because they already indicate 237 | // optional with `LABEL_OPTIONAL`. 238 | optional bool proto3_optional = 17; 239 | } 240 | 241 | // Describes a oneof. 242 | message OneofDescriptorProto { 243 | optional string name = 1; 244 | optional OneofOptions options = 2; 245 | } 246 | 247 | // Describes an enum type. 248 | message EnumDescriptorProto { 249 | optional string name = 1; 250 | 251 | repeated EnumValueDescriptorProto value = 2; 252 | 253 | optional EnumOptions options = 3; 254 | 255 | // Range of reserved numeric values. Reserved values may not be used by 256 | // entries in the same enum. Reserved ranges may not overlap. 257 | // 258 | // Note that this is distinct from DescriptorProto.ReservedRange in that it 259 | // is inclusive such that it can appropriately represent the entire int32 260 | // domain. 261 | message EnumReservedRange { 262 | optional int32 start = 1; // Inclusive. 263 | optional int32 end = 2; // Inclusive. 264 | } 265 | 266 | // Range of reserved numeric values. Reserved numeric values may not be used 267 | // by enum values in the same enum declaration. Reserved ranges may not 268 | // overlap. 269 | repeated EnumReservedRange reserved_range = 4; 270 | 271 | // Reserved enum value names, which may not be reused. A given name may only 272 | // be reserved once. 273 | repeated string reserved_name = 5; 274 | } 275 | 276 | // Describes a value within an enum. 277 | message EnumValueDescriptorProto { 278 | optional string name = 1; 279 | optional int32 number = 2; 280 | 281 | optional EnumValueOptions options = 3; 282 | } 283 | 284 | // Describes a service. 285 | message ServiceDescriptorProto { 286 | optional string name = 1; 287 | repeated MethodDescriptorProto method = 2; 288 | 289 | optional ServiceOptions options = 3; 290 | } 291 | 292 | // Describes a method of a service. 293 | message MethodDescriptorProto { 294 | optional string name = 1; 295 | 296 | // Input and output type names. These are resolved in the same way as 297 | // FieldDescriptorProto.type_name, but must refer to a message type. 298 | optional string input_type = 2; 299 | optional string output_type = 3; 300 | 301 | optional MethodOptions options = 4; 302 | 303 | // Identifies if client streams multiple client messages 304 | optional bool client_streaming = 5 [default = false]; 305 | // Identifies if server streams multiple server messages 306 | optional bool server_streaming = 6 [default = false]; 307 | } 308 | 309 | 310 | // =================================================================== 311 | // Options 312 | 313 | // Each of the definitions above may have "options" attached. These are 314 | // just annotations which may cause code to be generated slightly differently 315 | // or may contain hints for code that manipulates protocol messages. 316 | // 317 | // Clients may define custom options as extensions of the *Options messages. 318 | // These extensions may not yet be known at parsing time, so the parser cannot 319 | // store the values in them. Instead it stores them in a field in the *Options 320 | // message called uninterpreted_option. This field must have the same name 321 | // across all *Options messages. We then use this field to populate the 322 | // extensions when we build a descriptor, at which point all protos have been 323 | // parsed and so all extensions are known. 324 | // 325 | // Extension numbers for custom options may be chosen as follows: 326 | // * For options which will only be used within a single application or 327 | // organization, or for experimental options, use field numbers 50000 328 | // through 99999. It is up to you to ensure that you do not use the 329 | // same number for multiple options. 330 | // * For options which will be published and used publicly by multiple 331 | // independent entities, e-mail protobuf-global-extension-registry@google.com 332 | // to reserve extension numbers. Simply provide your project name (e.g. 333 | // Objective-C plugin) and your project website (if available) -- there's no 334 | // need to explain how you intend to use them. Usually you only need one 335 | // extension number. You can declare multiple options with only one extension 336 | // number by putting them in a sub-message. See the Custom Options section of 337 | // the docs for examples: 338 | // https://developers.google.com/protocol-buffers/docs/proto#options 339 | // If this turns out to be popular, a web service will be set up 340 | // to automatically assign option numbers. 341 | 342 | message FileOptions { 343 | 344 | // Sets the Java package where classes generated from this .proto will be 345 | // placed. By default, the proto package is used, but this is often 346 | // inappropriate because proto packages do not normally start with backwards 347 | // domain names. 348 | optional string java_package = 1; 349 | 350 | 351 | // Controls the name of the wrapper Java class generated for the .proto file. 352 | // That class will always contain the .proto file's getDescriptor() method as 353 | // well as any top-level extensions defined in the .proto file. 354 | // If java_multiple_files is disabled, then all the other classes from the 355 | // .proto file will be nested inside the single wrapper outer class. 356 | optional string java_outer_classname = 8; 357 | 358 | // If enabled, then the Java code generator will generate a separate .java 359 | // file for each top-level message, enum, and service defined in the .proto 360 | // file. Thus, these types will *not* be nested inside the wrapper class 361 | // named by java_outer_classname. However, the wrapper class will still be 362 | // generated to contain the file's getDescriptor() method as well as any 363 | // top-level extensions defined in the file. 364 | optional bool java_multiple_files = 10 [default = false]; 365 | 366 | // This option does nothing. 367 | optional bool java_generate_equals_and_hash = 20 [deprecated=true]; 368 | 369 | // If set true, then the Java2 code generator will generate code that 370 | // throws an exception whenever an attempt is made to assign a non-UTF-8 371 | // byte sequence to a string field. 372 | // Message reflection will do the same. 373 | // However, an extension field still accepts non-UTF-8 byte sequences. 374 | // This option has no effect on when used with the lite runtime. 375 | optional bool java_string_check_utf8 = 27 [default = false]; 376 | 377 | 378 | // Generated classes can be optimized for speed or code size. 379 | enum OptimizeMode { 380 | SPEED = 1; // Generate complete code for parsing, serialization, 381 | // etc. 382 | CODE_SIZE = 2; // Use ReflectionOps to implement these methods. 383 | LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. 384 | } 385 | optional OptimizeMode optimize_for = 9 [default = SPEED]; 386 | 387 | // Sets the Go package where structs generated from this .proto will be 388 | // placed. If omitted, the Go package will be derived from the following: 389 | // - The basename of the package import path, if provided. 390 | // - Otherwise, the package statement in the .proto file, if present. 391 | // - Otherwise, the basename of the .proto file, without extension. 392 | optional string go_package = 11; 393 | 394 | 395 | 396 | 397 | // Should generic services be generated in each language? "Generic" services 398 | // are not specific to any particular RPC system. They are generated by the 399 | // main code generators in each language (without additional plugins). 400 | // Generic services were the only kind of service generation supported by 401 | // early versions of google.protobuf. 402 | // 403 | // Generic services are now considered deprecated in favor of using plugins 404 | // that generate code specific to your particular RPC system. Therefore, 405 | // these default to false. Old code which depends on generic services should 406 | // explicitly set them to true. 407 | optional bool cc_generic_services = 16 [default = false]; 408 | optional bool java_generic_services = 17 [default = false]; 409 | optional bool py_generic_services = 18 [default = false]; 410 | optional bool php_generic_services = 42 [default = false]; 411 | 412 | // Is this file deprecated? 413 | // Depending on the target platform, this can emit Deprecated annotations 414 | // for everything in the file, or it will be completely ignored; in the very 415 | // least, this is a formalization for deprecating files. 416 | optional bool deprecated = 23 [default = false]; 417 | 418 | // Enables the use of arenas for the proto messages in this file. This applies 419 | // only to generated classes for C++. 420 | optional bool cc_enable_arenas = 31 [default = true]; 421 | 422 | 423 | // Sets the objective c class prefix which is prepended to all objective c 424 | // generated classes from this .proto. There is no default. 425 | optional string objc_class_prefix = 36; 426 | 427 | // Namespace for generated classes; defaults to the package. 428 | optional string csharp_namespace = 37; 429 | 430 | // By default Swift generators will take the proto package and CamelCase it 431 | // replacing '.' with underscore and use that to prefix the types/symbols 432 | // defined. When this options is provided, they will use this value instead 433 | // to prefix the types/symbols defined. 434 | optional string swift_prefix = 39; 435 | 436 | // Sets the php class prefix which is prepended to all php generated classes 437 | // from this .proto. Default is empty. 438 | optional string php_class_prefix = 40; 439 | 440 | // Use this option to change the namespace of php generated classes. Default 441 | // is empty. When this option is empty, the package name will be used for 442 | // determining the namespace. 443 | optional string php_namespace = 41; 444 | 445 | // Use this option to change the namespace of php generated metadata classes. 446 | // Default is empty. When this option is empty, the proto file name will be 447 | // used for determining the namespace. 448 | optional string php_metadata_namespace = 44; 449 | 450 | // Use this option to change the package of ruby generated classes. Default 451 | // is empty. When this option is not set, the package name will be used for 452 | // determining the ruby package. 453 | optional string ruby_package = 45; 454 | 455 | 456 | // The parser stores options it doesn't recognize here. 457 | // See the documentation for the "Options" section above. 458 | repeated UninterpretedOption uninterpreted_option = 999; 459 | 460 | // Clients can define custom options in extensions of this message. 461 | // See the documentation for the "Options" section above. 462 | extensions 1000 to max; 463 | 464 | reserved 38; 465 | } 466 | 467 | message MessageOptions { 468 | // Set true to use the old proto1 MessageSet wire format for extensions. 469 | // This is provided for backwards-compatibility with the MessageSet wire 470 | // format. You should not use this for any other reason: It's less 471 | // efficient, has fewer features, and is more complicated. 472 | // 473 | // The message must be defined exactly as follows: 474 | // message Foo { 475 | // option message_set_wire_format = true; 476 | // extensions 4 to max; 477 | // } 478 | // Note that the message cannot have any defined fields; MessageSets only 479 | // have extensions. 480 | // 481 | // All extensions of your type must be singular messages; e.g. they cannot 482 | // be int32s, enums, or repeated messages. 483 | // 484 | // Because this is an option, the above two restrictions are not enforced by 485 | // the protocol compiler. 486 | optional bool message_set_wire_format = 1 [default = false]; 487 | 488 | // Disables the generation of the standard "descriptor()" accessor, which can 489 | // conflict with a field of the same name. This is meant to make migration 490 | // from proto1 easier; new code should avoid fields named "descriptor". 491 | optional bool no_standard_descriptor_accessor = 2 [default = false]; 492 | 493 | // Is this message deprecated? 494 | // Depending on the target platform, this can emit Deprecated annotations 495 | // for the message, or it will be completely ignored; in the very least, 496 | // this is a formalization for deprecating messages. 497 | optional bool deprecated = 3 [default = false]; 498 | 499 | reserved 4, 5, 6; 500 | 501 | // Whether the message is an automatically generated map entry type for the 502 | // maps field. 503 | // 504 | // For maps fields: 505 | // map map_field = 1; 506 | // The parsed descriptor looks like: 507 | // message MapFieldEntry { 508 | // option map_entry = true; 509 | // optional KeyType key = 1; 510 | // optional ValueType value = 2; 511 | // } 512 | // repeated MapFieldEntry map_field = 1; 513 | // 514 | // Implementations may choose not to generate the map_entry=true message, but 515 | // use a native map in the target language to hold the keys and values. 516 | // The reflection APIs in such implementations still need to work as 517 | // if the field is a repeated message field. 518 | // 519 | // NOTE: Do not set the option in .proto files. Always use the maps syntax 520 | // instead. The option should only be implicitly set by the proto compiler 521 | // parser. 522 | optional bool map_entry = 7; 523 | 524 | reserved 8; // javalite_serializable 525 | reserved 9; // javanano_as_lite 526 | 527 | 528 | // The parser stores options it doesn't recognize here. See above. 529 | repeated UninterpretedOption uninterpreted_option = 999; 530 | 531 | // Clients can define custom options in extensions of this message. See above. 532 | extensions 1000 to max; 533 | } 534 | 535 | message FieldOptions { 536 | // The ctype option instructs the C++ code generator to use a different 537 | // representation of the field than it normally would. See the specific 538 | // options below. This option is not yet implemented in the open source 539 | // release -- sorry, we'll try to include it in a future version! 540 | optional CType ctype = 1 [default = STRING]; 541 | enum CType { 542 | // Default mode. 543 | STRING = 0; 544 | 545 | CORD = 1; 546 | 547 | STRING_PIECE = 2; 548 | } 549 | // The packed option can be enabled for repeated primitive fields to enable 550 | // a more efficient representation on the wire. Rather than repeatedly 551 | // writing the tag and type for each element, the entire array is encoded as 552 | // a single length-delimited blob. In proto3, only explicit setting it to 553 | // false will avoid using packed encoding. 554 | optional bool packed = 2; 555 | 556 | // The jstype option determines the JavaScript type used for values of the 557 | // field. The option is permitted only for 64 bit integral and fixed types 558 | // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING 559 | // is represented as JavaScript string, which avoids loss of precision that 560 | // can happen when a large value is converted to a floating point JavaScript. 561 | // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to 562 | // use the JavaScript "number" type. The behavior of the default option 563 | // JS_NORMAL is implementation dependent. 564 | // 565 | // This option is an enum to permit additional types to be added, e.g. 566 | // goog.math.Integer. 567 | optional JSType jstype = 6 [default = JS_NORMAL]; 568 | enum JSType { 569 | // Use the default type. 570 | JS_NORMAL = 0; 571 | 572 | // Use JavaScript strings. 573 | JS_STRING = 1; 574 | 575 | // Use JavaScript numbers. 576 | JS_NUMBER = 2; 577 | } 578 | 579 | // Should this field be parsed lazily? Lazy applies only to message-type 580 | // fields. It means that when the outer message is initially parsed, the 581 | // inner message's contents will not be parsed but instead stored in encoded 582 | // form. The inner message will actually be parsed when it is first accessed. 583 | // 584 | // This is only a hint. Implementations are free to choose whether to use 585 | // eager or lazy parsing regardless of the value of this option. However, 586 | // setting this option true suggests that the protocol author believes that 587 | // using lazy parsing on this field is worth the additional bookkeeping 588 | // overhead typically needed to implement it. 589 | // 590 | // This option does not affect the public interface of any generated code; 591 | // all method signatures remain the same. Furthermore, thread-safety of the 592 | // interface is not affected by this option; const methods remain safe to 593 | // call from multiple threads concurrently, while non-const methods continue 594 | // to require exclusive access. 595 | // 596 | // 597 | // Note that implementations may choose not to check required fields within 598 | // a lazy sub-message. That is, calling IsInitialized() on the outer message 599 | // may return true even if the inner message has missing required fields. 600 | // This is necessary because otherwise the inner message would have to be 601 | // parsed in order to perform the check, defeating the purpose of lazy 602 | // parsing. An implementation which chooses not to check required fields 603 | // must be consistent about it. That is, for any particular sub-message, the 604 | // implementation must either *always* check its required fields, or *never* 605 | // check its required fields, regardless of whether or not the message has 606 | // been parsed. 607 | optional bool lazy = 5 [default = false]; 608 | 609 | // Is this field deprecated? 610 | // Depending on the target platform, this can emit Deprecated annotations 611 | // for accessors, or it will be completely ignored; in the very least, this 612 | // is a formalization for deprecating fields. 613 | optional bool deprecated = 3 [default = false]; 614 | 615 | // For Google-internal migration only. Do not use. 616 | optional bool weak = 10 [default = false]; 617 | 618 | 619 | // The parser stores options it doesn't recognize here. See above. 620 | repeated UninterpretedOption uninterpreted_option = 999; 621 | 622 | // Clients can define custom options in extensions of this message. See above. 623 | extensions 1000 to max; 624 | 625 | reserved 4; // removed jtype 626 | } 627 | 628 | message OneofOptions { 629 | // The parser stores options it doesn't recognize here. See above. 630 | repeated UninterpretedOption uninterpreted_option = 999; 631 | 632 | // Clients can define custom options in extensions of this message. See above. 633 | extensions 1000 to max; 634 | } 635 | 636 | message EnumOptions { 637 | 638 | // Set this option to true to allow mapping different tag names to the same 639 | // value. 640 | optional bool allow_alias = 2; 641 | 642 | // Is this enum deprecated? 643 | // Depending on the target platform, this can emit Deprecated annotations 644 | // for the enum, or it will be completely ignored; in the very least, this 645 | // is a formalization for deprecating enums. 646 | optional bool deprecated = 3 [default = false]; 647 | 648 | reserved 5; // javanano_as_lite 649 | 650 | // The parser stores options it doesn't recognize here. See above. 651 | repeated UninterpretedOption uninterpreted_option = 999; 652 | 653 | // Clients can define custom options in extensions of this message. See above. 654 | extensions 1000 to max; 655 | } 656 | 657 | message EnumValueOptions { 658 | // Is this enum value deprecated? 659 | // Depending on the target platform, this can emit Deprecated annotations 660 | // for the enum value, or it will be completely ignored; in the very least, 661 | // this is a formalization for deprecating enum values. 662 | optional bool deprecated = 1 [default = false]; 663 | 664 | // The parser stores options it doesn't recognize here. See above. 665 | repeated UninterpretedOption uninterpreted_option = 999; 666 | 667 | // Clients can define custom options in extensions of this message. See above. 668 | extensions 1000 to max; 669 | } 670 | 671 | message ServiceOptions { 672 | 673 | // Note: Field numbers 1 through 32 are reserved for Google's internal RPC 674 | // framework. We apologize for hoarding these numbers to ourselves, but 675 | // we were already using them long before we decided to release Protocol 676 | // Buffers. 677 | 678 | // Is this service deprecated? 679 | // Depending on the target platform, this can emit Deprecated annotations 680 | // for the service, or it will be completely ignored; in the very least, 681 | // this is a formalization for deprecating services. 682 | optional bool deprecated = 33 [default = false]; 683 | 684 | // The parser stores options it doesn't recognize here. See above. 685 | repeated UninterpretedOption uninterpreted_option = 999; 686 | 687 | // Clients can define custom options in extensions of this message. See above. 688 | extensions 1000 to max; 689 | } 690 | 691 | message MethodOptions { 692 | 693 | // Note: Field numbers 1 through 32 are reserved for Google's internal RPC 694 | // framework. We apologize for hoarding these numbers to ourselves, but 695 | // we were already using them long before we decided to release Protocol 696 | // Buffers. 697 | 698 | // Is this method deprecated? 699 | // Depending on the target platform, this can emit Deprecated annotations 700 | // for the method, or it will be completely ignored; in the very least, 701 | // this is a formalization for deprecating methods. 702 | optional bool deprecated = 33 [default = false]; 703 | 704 | // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, 705 | // or neither? HTTP based RPC implementation may choose GET verb for safe 706 | // methods, and PUT verb for idempotent methods instead of the default POST. 707 | enum IdempotencyLevel { 708 | IDEMPOTENCY_UNKNOWN = 0; 709 | NO_SIDE_EFFECTS = 1; // implies idempotent 710 | IDEMPOTENT = 2; // idempotent, but may have side effects 711 | } 712 | optional IdempotencyLevel idempotency_level = 34 713 | [default = IDEMPOTENCY_UNKNOWN]; 714 | 715 | // The parser stores options it doesn't recognize here. See above. 716 | repeated UninterpretedOption uninterpreted_option = 999; 717 | 718 | // Clients can define custom options in extensions of this message. See above. 719 | extensions 1000 to max; 720 | } 721 | 722 | 723 | // A message representing a option the parser does not recognize. This only 724 | // appears in options protos created by the compiler::Parser class. 725 | // DescriptorPool resolves these when building Descriptor objects. Therefore, 726 | // options protos in descriptor objects (e.g. returned by Descriptor::options(), 727 | // or produced by Descriptor::CopyTo()) will never have UninterpretedOptions 728 | // in them. 729 | message UninterpretedOption { 730 | // The name of the uninterpreted option. Each string represents a segment in 731 | // a dot-separated name. is_extension is true iff a segment represents an 732 | // extension (denoted with parentheses in options specs in .proto files). 733 | // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents 734 | // "foo.(bar.baz).qux". 735 | message NamePart { 736 | required string name_part = 1; 737 | required bool is_extension = 2; 738 | } 739 | repeated NamePart name = 2; 740 | 741 | // The value of the uninterpreted option, in whatever type the tokenizer 742 | // identified it as during parsing. Exactly one of these should be set. 743 | optional string identifier_value = 3; 744 | optional uint64 positive_int_value = 4; 745 | optional int64 negative_int_value = 5; 746 | optional double double_value = 6; 747 | optional bytes string_value = 7; 748 | optional string aggregate_value = 8; 749 | } 750 | 751 | // =================================================================== 752 | // Optional source code info 753 | 754 | // Encapsulates information about the original source file from which a 755 | // FileDescriptorProto was generated. 756 | message SourceCodeInfo { 757 | // A Location identifies a piece of source code in a .proto file which 758 | // corresponds to a particular definition. This information is intended 759 | // to be useful to IDEs, code indexers, documentation generators, and similar 760 | // tools. 761 | // 762 | // For example, say we have a file like: 763 | // message Foo { 764 | // optional string foo = 1; 765 | // } 766 | // Let's look at just the field definition: 767 | // optional string foo = 1; 768 | // ^ ^^ ^^ ^ ^^^ 769 | // a bc de f ghi 770 | // We have the following locations: 771 | // span path represents 772 | // [a,i) [ 4, 0, 2, 0 ] The whole field definition. 773 | // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). 774 | // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). 775 | // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). 776 | // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). 777 | // 778 | // Notes: 779 | // - A location may refer to a repeated field itself (i.e. not to any 780 | // particular index within it). This is used whenever a set of elements are 781 | // logically enclosed in a single code segment. For example, an entire 782 | // extend block (possibly containing multiple extension definitions) will 783 | // have an outer location whose path refers to the "extensions" repeated 784 | // field without an index. 785 | // - Multiple locations may have the same path. This happens when a single 786 | // logical declaration is spread out across multiple places. The most 787 | // obvious example is the "extend" block again -- there may be multiple 788 | // extend blocks in the same scope, each of which will have the same path. 789 | // - A location's span is not always a subset of its parent's span. For 790 | // example, the "extendee" of an extension declaration appears at the 791 | // beginning of the "extend" block and is shared by all extensions within 792 | // the block. 793 | // - Just because a location's span is a subset of some other location's span 794 | // does not mean that it is a descendant. For example, a "group" defines 795 | // both a type and a field in a single declaration. Thus, the locations 796 | // corresponding to the type and field and their components will overlap. 797 | // - Code which tries to interpret locations should probably be designed to 798 | // ignore those that it doesn't understand, as more types of locations could 799 | // be recorded in the future. 800 | repeated Location location = 1; 801 | message Location { 802 | // Identifies which part of the FileDescriptorProto was defined at this 803 | // location. 804 | // 805 | // Each element is a field number or an index. They form a path from 806 | // the root FileDescriptorProto to the place where the definition. For 807 | // example, this path: 808 | // [ 4, 3, 2, 7, 1 ] 809 | // refers to: 810 | // file.message_type(3) // 4, 3 811 | // .field(7) // 2, 7 812 | // .name() // 1 813 | // This is because FileDescriptorProto.message_type has field number 4: 814 | // repeated DescriptorProto message_type = 4; 815 | // and DescriptorProto.field has field number 2: 816 | // repeated FieldDescriptorProto field = 2; 817 | // and FieldDescriptorProto.name has field number 1: 818 | // optional string name = 1; 819 | // 820 | // Thus, the above path gives the location of a field name. If we removed 821 | // the last element: 822 | // [ 4, 3, 2, 7 ] 823 | // this path refers to the whole field declaration (from the beginning 824 | // of the label to the terminating semicolon). 825 | repeated int32 path = 1 [packed = true]; 826 | 827 | // Always has exactly three or four elements: start line, start column, 828 | // end line (optional, otherwise assumed same as start line), end column. 829 | // These are packed into a single field for efficiency. Note that line 830 | // and column numbers are zero-based -- typically you will want to add 831 | // 1 to each before displaying to a user. 832 | repeated int32 span = 2 [packed = true]; 833 | 834 | // If this SourceCodeInfo represents a complete declaration, these are any 835 | // comments appearing before and after the declaration which appear to be 836 | // attached to the declaration. 837 | // 838 | // A series of line comments appearing on consecutive lines, with no other 839 | // tokens appearing on those lines, will be treated as a single comment. 840 | // 841 | // leading_detached_comments will keep paragraphs of comments that appear 842 | // before (but not connected to) the current element. Each paragraph, 843 | // separated by empty lines, will be one comment element in the repeated 844 | // field. 845 | // 846 | // Only the comment content is provided; comment markers (e.g. //) are 847 | // stripped out. For block comments, leading whitespace and an asterisk 848 | // will be stripped from the beginning of each line other than the first. 849 | // Newlines are included in the output. 850 | // 851 | // Examples: 852 | // 853 | // optional int32 foo = 1; // Comment attached to foo. 854 | // // Comment attached to bar. 855 | // optional int32 bar = 2; 856 | // 857 | // optional string baz = 3; 858 | // // Comment attached to baz. 859 | // // Another line attached to baz. 860 | // 861 | // // Comment attached to qux. 862 | // // 863 | // // Another line attached to qux. 864 | // optional double qux = 4; 865 | // 866 | // // Detached comment for corge. This is not leading or trailing comments 867 | // // to qux or corge because there are blank lines separating it from 868 | // // both. 869 | // 870 | // // Detached comment for corge paragraph 2. 871 | // 872 | // optional string corge = 5; 873 | // /* Block comment attached 874 | // * to corge. Leading asterisks 875 | // * will be removed. */ 876 | // /* Block comment attached to 877 | // * grault. */ 878 | // optional int32 grault = 6; 879 | // 880 | // // ignored detached comments. 881 | optional string leading_comments = 3; 882 | optional string trailing_comments = 4; 883 | repeated string leading_detached_comments = 6; 884 | } 885 | } 886 | 887 | // Describes the relationship between generated code and its original source 888 | // file. A GeneratedCodeInfo message is associated with only one generated 889 | // source file, but may contain references to different source .proto files. 890 | message GeneratedCodeInfo { 891 | // An Annotation connects some span of text in generated code to an element 892 | // of its generating .proto file. 893 | repeated Annotation annotation = 1; 894 | message Annotation { 895 | // Identifies the element in the original source .proto file. This field 896 | // is formatted the same as SourceCodeInfo.Location.path. 897 | repeated int32 path = 1 [packed = true]; 898 | 899 | // Identifies the filesystem path to the original source .proto. 900 | optional string source_file = 2; 901 | 902 | // Identifies the starting offset in bytes in the generated code 903 | // that relates to the identified object. 904 | optional int32 begin = 3; 905 | 906 | // Identifies the ending offset in bytes in the generated code that 907 | // relates to the identified offset. The end offset should be one past 908 | // the last relevant byte (so the length of the text = end - begin). 909 | optional int32 end = 4; 910 | } 911 | } 912 | --------------------------------------------------------------------------------