array( array('install_id'=>'char(32)','namespace'=>'char(32)','request_id'=>'bigint unsigned','stage'=>'varchar(16)','cursor_value'=>'bigint unsigned','body_hmac'=>'char(64)'), array('PRIMARY'=>array(0,array('install_id','namespace','request_id'))))); } public function installNativeTakeoverProgressSchema(): array { try{$this->connect(1);$this->validateSchema(); if($this->rows('SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?',array($this->profile['database'],$this->prefix.'native_takeover_progress_v1'))){throw new OG_Privacy_Case_Refusal('native_takeover_progress_schema_exists');} $this->sql('CREATE TABLE '.$this->table('native_takeover_progress_v1').' (install_id CHAR(32) NOT NULL,namespace CHAR(32) NOT NULL,request_id BIGINT UNSIGNED NOT NULL,stage VARCHAR(16) NOT NULL,cursor_value BIGINT UNSIGNED NOT NULL,body_hmac CHAR(64) NOT NULL,PRIMARY KEY(install_id,namespace,request_id)) ENGINE=InnoDB DEFAULT CHARSET=ascii COLLATE=ascii_bin'); $this->validateTableSpecs(self::takeoverProgressSpecs());return array('status'=>'native_takeover_progress_schema_installed','erasure_complete'=>false); }catch(Throwable $e){return $this->failure($e,'takeover_progress_install');}finally{$this->finish();} } private static function takeoverProgressMac(array $header,string $stage,string $cursor,string $secret): string { return hash_hmac('sha256',"og-native-takeover-progress-v1\n".json_encode($header+array('stage'=>$stage,'cursor'=>$cursor),JSON_THROW_ON_ERROR),hex2bin($secret)); } private function takeoverProgressState(array $b,array $header,string $secret): array { $rows=$this->rows('SELECT stage,CAST(cursor_value AS CHAR) AS cursor_text,body_hmac FROM '.$this->table('native_takeover_progress_v1').' WHERE install_id=? AND namespace=? AND request_id=? LIMIT 2',array($b['install'],$b['namespace'],$b['request_id'])); if(!$rows){return array('stage'=>'legacy_options','cursor'=>'0','saved'=>false);} $row=$rows[0];$cursor=$row['cursor_text']??null; if(count($rows)!==1||!in_array($row['stage'],array('legacy_options','legacy_users','signal_outbox','finished'),true)||!is_string($cursor)||!preg_match('/^(?:0|[1-9][0-9]{0,18})$/D',$cursor)||(string)(int)$cursor!==$cursor||!hash_equals(self::takeoverProgressMac($header,$row['stage'],$cursor,$secret),$row['body_hmac'])){throw new OG_Privacy_Case_Refusal('native_takeover_progress_damaged');} return array('stage'=>$row['stage'],'cursor'=>$cursor,'saved'=>true,'body_hmac'=>$row['body_hmac']); } private function takeoverProgressContext(array $context,int $requestId): array { list($b,$secret,$op,$request,$header)=$this->localFlagContext($context,$requestId);$this->validateTableSpecs(array_merge(self::takeoverProgressSpecs(),self::flagProjectionSpecs())); $projection=$this->flagProjectionRow($b,$header,$secret);if($projection===null||$projection['state']!=='applied'){throw new OG_Privacy_Case_Refusal('native_takeover_projection_required');} return array($b,$secret,$header); } public function readNativeTakeoverProgress(array $context,int $requestId): array { try{list($b,$secret,$header)=$this->takeoverProgressContext($context,$requestId);$state=$this->takeoverProgressState($b,$header,$secret);$this->commit(); return array('status'=>'native_takeover_progress','stage'=>$state['stage'],'cursor'=>(int)$state['cursor'],'erasure_complete'=>false); }catch(Throwable $e){return array_merge($this->failure($e,'takeover_progress_read'),array('erasure_complete'=>false));}finally{$this->finish();} } public function advanceNativeTakeoverProgress(array $context,int $requestId,string $event,int $cursor,array $cleanup): array { try{if(!in_array($event,array('legacy_options','legacy_users','signal_outbox'),true)||$cursor<0||($cleanup['status']??null)!==($event==='signal_outbox'?'native_signal_outbox_page_scrubbed':'legacy_legal_page_purged')||!is_int($cleanup['next_cursor']??null)||!is_bool($cleanup['has_more']??null)){throw new OG_Privacy_Case_Refusal('native_takeover_progress_input');} list($b,$secret,$header)=$this->takeoverProgressContext($context,$requestId);$old=$this->takeoverProgressState($b,$header,$secret); if($old['stage']!==$event||(int)$old['cursor']!==$cursor){throw new OG_Privacy_Case_Refusal('native_takeover_progress_conflict');} $nextCursor=$cleanup['next_cursor'];if($cleanup['has_more']&&$nextCursor<=$cursor){throw new OG_Privacy_Case_Refusal('native_takeover_progress_input');} $stage=$cleanup['has_more']?$event:($event==='legacy_options'?'legacy_users':($event==='legacy_users'?'signal_outbox':'finished'));$next=$cleanup['has_more']?$nextCursor:0;$nextString=(string)$next;$mac=self::takeoverProgressMac($header,$stage,$nextString,$secret);$args=array($b['install'],$b['namespace'],$requestId); if(!$old['saved']){$changed=$this->sql('INSERT INTO '.$this->table('native_takeover_progress_v1').' (install_id,namespace,request_id,stage,cursor_value,body_hmac) VALUES (?,?,?,?,?,?)',array_merge($args,array($stage,$next,$mac)));} else{$changed=$this->sql('UPDATE '.$this->table('native_takeover_progress_v1').' SET stage=?,cursor_value=?,body_hmac=? WHERE install_id=? AND namespace=? AND request_id=? AND stage=? AND cursor_value=? AND body_hmac=?',array($stage,$next,$mac,$b['install'],$b['namespace'],$requestId,$old['stage'],$cursor,$old['body_hmac']));} if($changed!==1){throw new OG_Privacy_Case_Refusal('native_takeover_progress_conflict');}$saved=$this->takeoverProgressState($b,$header,$secret); if($saved['stage']!==$stage||(int)$saved['cursor']!==$next){throw new OG_Privacy_Case_Refusal('native_takeover_progress_damaged');}$this->commit();return array('status'=>'native_takeover_progress_advanced','stage'=>$stage,'cursor'=>$next,'erasure_complete'=>false); }catch(Throwable $e){return array_merge($this->failure($e,'takeover_progress_advance'),array('erasure_complete'=>false));}finally{$this->finish();} } }