@@ -1016,6 +1016,152 @@ func (rt *Runtime) SoftHashVideoV0(ctx context.Context, frameSigs [][]int32, bit
10161016 return rt .callByteBufferResult (ctx , "iscc_soft_hash_video_v0" , sretPtr )
10171017}
10181018
1019+ // ── Streaming hashers ────────────────────────────────────────────────────────
1020+
1021+ // DataHasher provides streaming Data-Code generation via the WASM FFI.
1022+ // Create with Runtime.NewDataHasher, feed data with Update, and retrieve the
1023+ // ISCC code with Finalize. Close releases the WASM-side memory.
1024+ type DataHasher struct {
1025+ rt * Runtime
1026+ ptr uint32 // opaque WASM-side FfiDataHasher pointer
1027+ }
1028+
1029+ // InstanceHasher provides streaming Instance-Code generation via the WASM FFI.
1030+ // Create with Runtime.NewInstanceHasher, feed data with Update, and retrieve the
1031+ // ISCC code with Finalize. Close releases the WASM-side memory.
1032+ type InstanceHasher struct {
1033+ rt * Runtime
1034+ ptr uint32 // opaque WASM-side FfiInstanceHasher pointer
1035+ }
1036+
1037+ // NewDataHasher creates a streaming Data-Code hasher.
1038+ // The caller must call Close when done, even after Finalize.
1039+ func (rt * Runtime ) NewDataHasher (ctx context.Context ) (* DataHasher , error ) {
1040+ fn := rt .mod .ExportedFunction ("iscc_data_hasher_new" )
1041+ results , err := fn .Call (ctx )
1042+ if err != nil {
1043+ return nil , fmt .Errorf ("iscc_data_hasher_new: %w" , err )
1044+ }
1045+ ptr := uint32 (results [0 ])
1046+ if ptr == 0 {
1047+ return nil , fmt .Errorf ("iscc_data_hasher_new: returned NULL: %s" , rt .lastError (ctx ))
1048+ }
1049+ return & DataHasher {rt : rt , ptr : ptr }, nil
1050+ }
1051+
1052+ // NewInstanceHasher creates a streaming Instance-Code hasher.
1053+ // The caller must call Close when done, even after Finalize.
1054+ func (rt * Runtime ) NewInstanceHasher (ctx context.Context ) (* InstanceHasher , error ) {
1055+ fn := rt .mod .ExportedFunction ("iscc_instance_hasher_new" )
1056+ results , err := fn .Call (ctx )
1057+ if err != nil {
1058+ return nil , fmt .Errorf ("iscc_instance_hasher_new: %w" , err )
1059+ }
1060+ ptr := uint32 (results [0 ])
1061+ if ptr == 0 {
1062+ return nil , fmt .Errorf ("iscc_instance_hasher_new: returned NULL: %s" , rt .lastError (ctx ))
1063+ }
1064+ return & InstanceHasher {rt : rt , ptr : ptr }, nil
1065+ }
1066+
1067+ // Update feeds data into the DataHasher.
1068+ // Can be called multiple times before Finalize. Returns an error if the
1069+ // hasher has already been finalized.
1070+ func (h * DataHasher ) Update (ctx context.Context , data []byte ) error {
1071+ dataPtr , dataSize , err := h .rt .writeBytes (ctx , data )
1072+ if err != nil {
1073+ return err
1074+ }
1075+ defer func () { _ = h .rt .dealloc (ctx , dataPtr , dataSize ) }()
1076+
1077+ fn := h .rt .mod .ExportedFunction ("iscc_data_hasher_update" )
1078+ results , err := fn .Call (ctx , uint64 (h .ptr ), uint64 (dataPtr ), uint64 (dataSize ))
1079+ if err != nil {
1080+ return fmt .Errorf ("iscc_data_hasher_update: %w" , err )
1081+ }
1082+ if results [0 ] == 0 {
1083+ return fmt .Errorf ("iscc_data_hasher_update: %s" , h .rt .lastError (ctx ))
1084+ }
1085+ return nil
1086+ }
1087+
1088+ // Finalize completes the hashing and returns the ISCC Data-Code string.
1089+ // After Finalize, Update and Finalize will return errors. The caller must
1090+ // still call Close to free WASM-side memory.
1091+ func (h * DataHasher ) Finalize (ctx context.Context , bits uint32 ) (string , error ) {
1092+ fn := h .rt .mod .ExportedFunction ("iscc_data_hasher_finalize" )
1093+ results , err := fn .Call (ctx , uint64 (h .ptr ), uint64 (bits ))
1094+ if err != nil {
1095+ return "" , fmt .Errorf ("iscc_data_hasher_finalize: %w" , err )
1096+ }
1097+ return h .rt .callStringResult (ctx , "iscc_data_hasher_finalize" , results )
1098+ }
1099+
1100+ // Close releases the WASM-side DataHasher memory.
1101+ // Safe to call multiple times. Sets the internal pointer to 0 to prevent
1102+ // double-free.
1103+ func (h * DataHasher ) Close (ctx context.Context ) error {
1104+ if h .ptr == 0 {
1105+ return nil
1106+ }
1107+ fn := h .rt .mod .ExportedFunction ("iscc_data_hasher_free" )
1108+ _ , err := fn .Call (ctx , uint64 (h .ptr ))
1109+ h .ptr = 0
1110+ if err != nil {
1111+ return fmt .Errorf ("iscc_data_hasher_free: %w" , err )
1112+ }
1113+ return nil
1114+ }
1115+
1116+ // Update feeds data into the InstanceHasher.
1117+ // Can be called multiple times before Finalize. Returns an error if the
1118+ // hasher has already been finalized.
1119+ func (h * InstanceHasher ) Update (ctx context.Context , data []byte ) error {
1120+ dataPtr , dataSize , err := h .rt .writeBytes (ctx , data )
1121+ if err != nil {
1122+ return err
1123+ }
1124+ defer func () { _ = h .rt .dealloc (ctx , dataPtr , dataSize ) }()
1125+
1126+ fn := h .rt .mod .ExportedFunction ("iscc_instance_hasher_update" )
1127+ results , err := fn .Call (ctx , uint64 (h .ptr ), uint64 (dataPtr ), uint64 (dataSize ))
1128+ if err != nil {
1129+ return fmt .Errorf ("iscc_instance_hasher_update: %w" , err )
1130+ }
1131+ if results [0 ] == 0 {
1132+ return fmt .Errorf ("iscc_instance_hasher_update: %s" , h .rt .lastError (ctx ))
1133+ }
1134+ return nil
1135+ }
1136+
1137+ // Finalize completes the hashing and returns the ISCC Instance-Code string.
1138+ // After Finalize, Update and Finalize will return errors. The caller must
1139+ // still call Close to free WASM-side memory.
1140+ func (h * InstanceHasher ) Finalize (ctx context.Context , bits uint32 ) (string , error ) {
1141+ fn := h .rt .mod .ExportedFunction ("iscc_instance_hasher_finalize" )
1142+ results , err := fn .Call (ctx , uint64 (h .ptr ), uint64 (bits ))
1143+ if err != nil {
1144+ return "" , fmt .Errorf ("iscc_instance_hasher_finalize: %w" , err )
1145+ }
1146+ return h .rt .callStringResult (ctx , "iscc_instance_hasher_finalize" , results )
1147+ }
1148+
1149+ // Close releases the WASM-side InstanceHasher memory.
1150+ // Safe to call multiple times. Sets the internal pointer to 0 to prevent
1151+ // double-free.
1152+ func (h * InstanceHasher ) Close (ctx context.Context ) error {
1153+ if h .ptr == 0 {
1154+ return nil
1155+ }
1156+ fn := h .rt .mod .ExportedFunction ("iscc_instance_hasher_free" )
1157+ _ , err := fn .Call (ctx , uint64 (h .ptr ))
1158+ h .ptr = 0
1159+ if err != nil {
1160+ return fmt .Errorf ("iscc_instance_hasher_free: %w" , err )
1161+ }
1162+ return nil
1163+ }
1164+
10191165// GenIsccCodeV0 generates a composite ISCC-CODE from individual unit codes.
10201166func (rt * Runtime ) GenIsccCodeV0 (ctx context.Context , codes []string ) (string , error ) {
10211167 codesPtr , codesCount , cleanup , err := rt .writeStringArray (ctx , codes )
0 commit comments